From patchwork Mon Nov 28 03:34:35 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Martin Sebor X-Patchwork-Id: 84352 Delivered-To: patch@linaro.org Received: by 10.140.20.101 with SMTP id 92csp967162qgi; Sun, 27 Nov 2016 19:35:10 -0800 (PST) X-Received: by 10.98.4.134 with SMTP id 128mr19684288pfe.156.1480304110506; Sun, 27 Nov 2016 19:35:10 -0800 (PST) Return-Path: Received: from sourceware.org (server1.sourceware.org. [209.132.180.131]) by mx.google.com with ESMTPS id w3si53387015pgb.4.2016.11.27.19.35.10 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sun, 27 Nov 2016 19:35:10 -0800 (PST) Received-SPF: pass (google.com: domain of gcc-patches-return-442744-patch=linaro.org@gcc.gnu.org designates 209.132.180.131 as permitted sender) client-ip=209.132.180.131; Authentication-Results: mx.google.com; dkim=pass header.i=@gcc.gnu.org; spf=pass (google.com: domain of gcc-patches-return-442744-patch=linaro.org@gcc.gnu.org designates 209.132.180.131 as permitted sender) smtp.mailfrom=gcc-patches-return-442744-patch=linaro.org@gcc.gnu.org; dmarc=fail (p=NONE dis=NONE) header.from=gmail.com DomainKey-Signature: a=rsa-sha1; c=nofws; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender:to :from:subject:message-id:date:mime-version:content-type; q=dns; s=default; b=GvrAjQ4wfO1G5vJHr3QFWpPQBspPM1rO4TMq+Gt6SX6qIXnMTX VXwMi0/1Qz6EVDEw+sqlnHhQ15njMKZMGRiNI95ppA5c3GGVOJmk6GbD0EZOmLA9 Gdj5/KgcSavB5DCAvSH62g6GndgfrJHu+HMhqZo+pp/IX1W4brAvQAZ6c= DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender:to :from:subject:message-id:date:mime-version:content-type; s= default; bh=7jqEVka6nb/0OseOfREmSxEhDw4=; b=uCLxmBMgAogAL6PHuOhy S8loFNH0Qqp24+L5Oz3IcJiw8mRrW+SJjsm2oCPNQR+P0JdO4JC8ic80WwToTI07 2SDO5NeEvgc67weXqre1rHNEpmQCMGslGZqH3bQMt34S8nSNHoAOisrfbRCB1XsH ulPmx5UBbboOIo/euiTyA5M= Received: (qmail 85010 invoked by alias); 28 Nov 2016 03:34:52 -0000 Mailing-List: contact gcc-patches-help@gcc.gnu.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Archive: List-Post: List-Help: Sender: gcc-patches-owner@gcc.gnu.org Delivered-To: mailing list gcc-patches@gcc.gnu.org Received: (qmail 84991 invoked by uid 89); 28 Nov 2016 03:34:51 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-2.5 required=5.0 tests=AWL, BAYES_00, FREEMAIL_FROM, RCVD_IN_DNSWL_LOW, SPF_PASS autolearn=ham version=3.3.2 spammy=sole, diagnosed, UD:E, preceded X-HELO: mail-qt0-f172.google.com Received: from mail-qt0-f172.google.com (HELO mail-qt0-f172.google.com) (209.85.216.172) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Mon, 28 Nov 2016 03:34:40 +0000 Received: by mail-qt0-f172.google.com with SMTP id w33so111175901qtc.3 for ; Sun, 27 Nov 2016 19:34:40 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:to:from:subject:message-id:date:user-agent :mime-version; bh=58HWyA8LkcEBXRCPST0LbcqOcANQXOdZUSQrqirCCyM=; b=TXplYCgaFbuk+AdHxYxhdpKo6EqMTZYsb4PrULMAdQY+pZCROoAvfdoGSequziS8Az 92B8ouG0QQVys2usWMDtrbZICUCZrVp4FqbVSx5hbyubNsKpUNlwe4wEdVqk/vCJ4FUF OoNzYH58RH5bfbjkcg5yV+meMFVTeHGim9ls3Ed300JFSecUSjzg/pbXIlCJgcqNgsYV n7T8VjDLgYOuDFCyWkMwxiHT3TFWjt/wmxyjQPnDq4XxnzDGliyVE3XZ/E2XmwhBhfxW ggTTdjunuDSlTFirD8O/00AHubO6+eZ9d04wL0eKIyOc6SKSLJrKghu8ui2f5lZevobS d10w== X-Gm-Message-State: AKaTC00iAw/ZsuqPPlyUs2mPfOROPUBgqzQ6KIilAH1rs8D6CFw9TTzA/7h5zmPW+opyxQ== X-Received: by 10.200.52.221 with SMTP id x29mr18498317qtb.19.1480304078529; Sun, 27 Nov 2016 19:34:38 -0800 (PST) Received: from [192.168.0.26] (75-166-206-79.hlrn.qwest.net. [75.166.206.79]) by smtp.gmail.com with ESMTPSA id 124sm27527069qki.14.2016.11.27.19.34.37 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sun, 27 Nov 2016 19:34:38 -0800 (PST) To: Gcc Patch List From: Martin Sebor Subject: [PATCH] correct handling of non-constant width and precision (pr 78521) Message-ID: <9b412cbb-1cb0-6341-5b85-78f235f7ae6f@gmail.com> Date: Sun, 27 Nov 2016 20:34:35 -0700 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.3.0 MIME-Version: 1.0 X-IsSubscribed: yes PR 78521 notes that the gimple-ssa-sprintf pass doesn't do the right thing (i.e., the -Wformat-length and -fprintf-return-value options behave incorrectly) when a conversion specification includes a width or precision with a non-constant value. The code treats such cases as if they were not provided which is incorrect and results in the wrong bytes counts in warning messages and in the wrong ranges being generated for such calls (or in the case sprintf(0, 0, ...) for some such calls being eliminated). The attached patch corrects the handling of these cases, plus a couple of other edge cases in the same area: it adjusts the parser to accept precision in the form of just a period with no asterisk or decimal digits after it (this sets the precision to zero), and corrects the handling of zero precision and zero argument in integer directives to produce no bytes on output. Finally, the patch also tightens up the constraint on the upper bound of bounded functions like snprintf to be INT_MAX. The functions cannot produce output in excess of INT_MAX + 1 bytes and some implementations (e.g., Solaris) fail with EINVAL when the bound is INT_MAX or more. This is the subject of PR 78520. Thanks Martin PR middle-end/78521 - [7 Regression] incorrect byte count in -Wformat-length warning with non-constant width or precision PR middle-end/78520 - missing warning for snprintf with size greater than INT_MAX gcc/ChangeLog: PR middle-end/78521 PR middle-end/78520 * gimple-ssa-sprintf.c (target_max_value): Remove. (target_int_max, target_size_max): Use TYPE_MAX_VALUE. (get_width_and_precision): New function. (format_integer, format_floating, get_string_length, format_string): Correct handling of width and precision with unknown value. (format_directive): Add warning. (pass_sprintf_length::compute_format_length): Allow for precision to consist of a sole period with no asterisk or digits after it. gcc/testsuite/ChangeLog: PR middle-end/78521 PR middle-end/78520 * gcc.dg/tree-ssa/builtin-sprintf-5.c: Add test cases. * gcc.dg/tree-ssa/builtin-sprintf-6.c: New test. * gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: Add test cases. * gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: Add test cases. diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c index dc2b66d..9f07503 100644 --- a/gcc/gimple-ssa-sprintf.c +++ b/gcc/gimple-ssa-sprintf.c @@ -235,23 +235,12 @@ target_int_min () return int_min; } -/* Return the largest value for TYPE on the target. */ - -static unsigned HOST_WIDE_INT -target_max_value (tree type) -{ - const unsigned HOST_WIDE_INT max_value - = HOST_WIDE_INT_M1U >> (HOST_BITS_PER_WIDE_INT - - TYPE_PRECISION (type) + 1); - return max_value; -} - /* Return the value of INT_MAX for the target. */ static inline unsigned HOST_WIDE_INT target_int_max () { - return target_max_value (integer_type_node); + return tree_to_uhwi (TYPE_MAX_VALUE (integer_type_node)); } /* Return the value of SIZE_MAX for the target. */ @@ -259,7 +248,7 @@ target_int_max () static inline unsigned HOST_WIDE_INT target_size_max () { - return target_max_value (size_type_node); + return tree_to_uhwi (TYPE_MAX_VALUE (size_type_node)); } /* Return the constant initial value of DECL if available or DECL @@ -843,6 +832,43 @@ format_pointer (const conversion_spec &spec, tree arg) return res; } +/* Set *PWIDTH and *PPREC according to the width and precision specified + in SPEC. Each is set to HOST_WIDE_INT_MIN when the corresponding + field is specified but unknown, to zero for width and -1, respectively + when it's not specified, or to a non-negative value corresponding to + the known value. */ +static void +get_width_and_precision (const conversion_spec &spec, + HOST_WIDE_INT *pwidth, HOST_WIDE_INT *pprec) +{ + HOST_WIDE_INT width = spec.have_width ? spec.width : 0; + HOST_WIDE_INT prec = spec.have_precision ? spec.precision : -1; + + if (spec.star_width) + { + if (TREE_CODE (spec.star_width) == INTEGER_CST) + width = abs (tree_to_shwi (spec.star_width)); + else + width = HOST_WIDE_INT_MIN; + } + + if (spec.star_precision) + { + if (TREE_CODE (spec.star_precision) == INTEGER_CST) + { + prec = tree_to_shwi (spec.star_precision); + if (prec < 0) + prec = 0; + } + else + prec = HOST_WIDE_INT_MIN; + } + + *pwidth = width; + *pprec = prec; +} + + /* Return a range representing the minimum and maximum number of bytes that the conversion specification SPEC will write on output for the integer argument ARG when non-null. ARG may be null (for vararg @@ -860,18 +886,10 @@ format_integer (const conversion_spec &spec, tree arg) if (!intmax_type_node) build_intmax_type_nodes (&intmax_type_node, &uintmax_type_node); - /* Set WIDTH and PRECISION to either the values in the format - specification or to zero. */ - int width = spec.have_width ? spec.width : 0; - int prec = spec.have_precision ? spec.precision : 0; - - if (spec.star_width) - width = (TREE_CODE (spec.star_width) == INTEGER_CST - ? tree_to_shwi (spec.star_width) : 0); - - if (spec.star_precision) - prec = (TREE_CODE (spec.star_precision) == INTEGER_CST - ? tree_to_shwi (spec.star_precision) : 0); + /* Set WIDTH and PRECISION based on the specification. */ + HOST_WIDE_INT width; + HOST_WIDE_INT prec; + get_width_and_precision (spec, &width, &prec); bool sign = spec.specifier == 'd' || spec.specifier == 'i'; @@ -940,15 +958,8 @@ format_integer (const conversion_spec &spec, tree arg) } else if (TREE_CODE (arg) == INTEGER_CST) { - /* The minimum and maximum number of bytes produced by - the directive. */ - fmtresult res; - /* When a constant argument has been provided use its value rather than type to determine the length of the output. */ - res.bounded = true; - res.constant = true; - res.knownrange = true; /* Base to format the number in. */ int base; @@ -981,25 +992,56 @@ format_integer (const conversion_spec &spec, tree arg) gcc_unreachable (); } - /* Convert the argument to the type of the directive. */ - arg = fold_convert (dirtype, arg); + int len; + + if ((prec == HOST_WIDE_INT_MIN || prec == 0) && integer_zerop (arg)) + { + /* As a special case, a precision of zero with an argument + of zero results in zero bytes regardless of flags (with + width having the normal effect). This must extend to + the case of a specified precision with an unknown value + because it can be zero. */ + len = 0; + } + else + { + /* Convert the argument to the type of the directive. */ + arg = fold_convert (dirtype, arg); - maybesign |= spec.get_flag ('+'); + maybesign |= spec.get_flag ('+'); - /* True when a conversion is preceded by a prefix indicating the base - of the argument (octal or hexadecimal). */ - bool maybebase = spec.get_flag ('#'); - int len = tree_digits (arg, base, maybesign, maybebase); + /* True when a conversion is preceded by a prefix indicating the base + of the argument (octal or hexadecimal). */ + bool maybebase = spec.get_flag ('#'); + len = tree_digits (arg, base, maybesign, maybebase); - if (len < prec) - len = prec; + if (len < prec) + len = prec; + } if (len < width) len = width; - res.range.max = len; - res.range.min = res.range.max; - res.bounded = true; + /* The minimum and maximum number of bytes produced by the directive. */ + fmtresult res; + + res.range.min = len; + + /* The upper bound of the number of bytes is unlimited when either + width or precision is specified but its value is unknown, and + the same as the lower bound otherwise. */ + if (width == HOST_WIDE_INT_MIN || prec == HOST_WIDE_INT_MIN) + { + res.range.max = HOST_WIDE_INT_MAX; + } + else + { + res.range.max = len; + res.bounded = true; + res.constant = true; + res.knownrange = true; + res.bounded = true; + } return res; } @@ -1110,8 +1152,10 @@ format_integer (const conversion_spec &spec, tree arg) or one whose value range cannot be determined, create a T_MIN constant if the argument's type is signed and T_MAX otherwise, and use those to compute the range of bytes that the directive - can output. */ - argmin = build_int_cst (argtype, 1); + can output. When precision is specified but unknown, use zero + as the minimum since it results in no bytes on output (unless + width is specified to be greater than 0). */ + argmin = build_int_cst (argtype, prec != HOST_WIDE_INT_MIN); int typeprec = TYPE_PRECISION (dirtype); int argprec = TYPE_PRECISION (argtype); @@ -1261,11 +1305,13 @@ format_floating (const conversion_spec &spec, int width, int prec) { /* The minimum output is "0x.p+0". */ res.range.min = 6 + (prec > 0 ? prec : 0); - res.range.max = format_floating_max (type, 'a', prec); + res.range.max = (width == INT_MIN + ? HOST_WIDE_INT_MAX + : format_floating_max (type, 'a', prec)); /* The output of "%a" is fully specified only when precision - is explicitly specified. */ - res.bounded = -1 < prec; + is explicitly specified and width isn't unknown. */ + res.bounded = INT_MIN != width && -1 < prec; break; } @@ -1278,13 +1324,16 @@ format_floating (const conversion_spec &spec, int width, int prec) res.range.min = (sign + 1 /* unit */ + (prec < 0 ? 7 : prec ? prec + 1 : 0) + 2 /* e+ */ + 2); - /* The maximum output is the minimum plus sign (unless already - included), plus the difference between the minimum exponent - of 2 and the maximum exponent for the type. */ - res.range.max = res.range.min + !sign + logexpdigs - 2; - - /* "%e" is fully specified and the range of bytes is bounded. */ - res.bounded = true; + /* Unless width is uknown the maximum output is the minimum plus + sign (unless already included), plus the difference between + the minimum exponent of 2 and the maximum exponent for the type. */ + res.range.max = (width == INT_MIN + ? HOST_WIDE_INT_M1U + : res.range.min + !sign + logexpdigs - 2); + + /* "%e" is fully specified and the range of bytes is bounded + unless width is unknown. */ + res.bounded = INT_MIN != width; break; } @@ -1300,10 +1349,11 @@ format_floating (const conversion_spec &spec, int width, int prec) format_floating_max (double_type_node, 'f'), format_floating_max (long_double_type_node, 'f') }; - res.range.max = f_max [ldbl]; + res.range.max = width == INT_MIN ? HOST_WIDE_INT_MAX : f_max [ldbl]; - /* "%f" is fully specified and the range of bytes is bounded. */ - res.bounded = true; + /* "%f" is fully specified and the range of bytes is bounded + unless width is unknown. */ + res.bounded = INT_MIN != width; break; } case 'G': @@ -1317,10 +1367,11 @@ format_floating (const conversion_spec &spec, int width, int prec) format_floating_max (double_type_node, 'g'), format_floating_max (long_double_type_node, 'g') }; - res.range.max = g_max [ldbl]; + res.range.max = width == INT_MIN ? HOST_WIDE_INT_MAX : g_max [ldbl]; - /* "%g" is fully specified and the range of bytes is bounded. */ - res.bounded = true; + /* "%g" is fully specified and the range of bytes is bounded + unless width is unknown. */ + res.bounded = INT_MIN != width; break; } @@ -1346,6 +1397,9 @@ format_floating (const conversion_spec &spec, int width, int prec) static fmtresult format_floating (const conversion_spec &spec, tree arg) { + /* Set WIDTH to -1 when it's not specified, to INT_MIN when it is + specified by the asterisk to an unknown value, and otherwise to + a non-negative value corresponding to the specified width. */ int width = -1; int prec = -1; @@ -1358,12 +1412,13 @@ format_floating (const conversion_spec &spec, tree arg) else if (spec.star_width) { if (TREE_CODE (spec.star_width) == INTEGER_CST) - width = tree_to_shwi (spec.star_width); - else { - res.range.min = res.range.max = HOST_WIDE_INT_M1U; - return res; + width = tree_to_shwi (spec.star_width); + if (width < 0) + width = -width; } + else + width = INT_MIN; } if (spec.have_precision) @@ -1374,6 +1429,7 @@ format_floating (const conversion_spec &spec, tree arg) prec = tree_to_shwi (spec.star_precision); else { + /* FIXME: Handle non-constant precision. */ res.range.min = res.range.max = HOST_WIDE_INT_M1U; return res; } @@ -1413,9 +1469,9 @@ format_floating (const conversion_spec &spec, tree arg) *pfmt++ = *pf; /* Append width when specified and precision. */ - if (width != -1) + if (-1 < width) pfmt += sprintf (pfmt, "%i", width); - if (prec != -1) + if (-1 < prec) pfmt += sprintf (pfmt, ".%i", prec); /* Append the MPFR 'R' floating type specifier (no length modifier @@ -1442,16 +1498,24 @@ format_floating (const conversion_spec &spec, tree arg) *minmax[i] = mpfr_snprintf (NULL, 0, fmtstr, mpfrval); } + /* The range of output is known even if the result isn't bounded. */ + if (width == INT_MIN) + { + res.knownrange = false; + res.range.max = HOST_WIDE_INT_MAX; + } + else + res.knownrange = true; + /* The output of all directives except "%a" is fully specified and so the result is bounded unless it exceeds INT_MAX. For "%a" the output is fully specified only when precision is explicitly specified. */ - res.bounded = ((TOUPPER (spec.specifier) != 'A' - || (0 <= prec && (unsigned) prec < target_int_max ())) + res.bounded = (res.knownrange + && (TOUPPER (spec.specifier) != 'A' + || (0 <= prec && (unsigned) prec < target_int_max ())) && res.range.min < target_int_max ()); - /* The range of output is known even if the result isn't bounded. */ - res.knownrange = true; return res; } @@ -1521,20 +1585,10 @@ get_string_length (tree str) static fmtresult format_string (const conversion_spec &spec, tree arg) { - unsigned width = spec.have_width && spec.width > 0 ? spec.width : 0; - int prec = spec.have_precision ? spec.precision : -1; - - if (spec.star_width) - { - width = (TREE_CODE (spec.star_width) == INTEGER_CST - ? tree_to_shwi (spec.star_width) : 0); - if (width > INT_MAX) - width = 0; - } - - if (spec.star_precision) - prec = (TREE_CODE (spec.star_precision) == INTEGER_CST - ? tree_to_shwi (spec.star_precision) : -1); + /* Set WIDTH and PRECISION based on the specification. */ + HOST_WIDE_INT width; + HOST_WIDE_INT prec; + get_width_and_precision (spec, &width, &prec); fmtresult res; @@ -1594,11 +1648,12 @@ format_string (const conversion_spec &spec, tree arg) res.range = slen.range; /* The output of "%s" and "%ls" directives with a constant - string is in a known range. For "%s" it is the length - of the string. For "%ls" it is in the range [length, - length * MB_LEN_MAX]. (The final range can be further - constrained by width and precision but it's always known.) */ - res.knownrange = true; + string is in a known range unless width of an unknown value + is specified. For "%s" it is the length of the string. For + "%ls" it is in the range [length, length * MB_LEN_MAX]. + (The final range can be further constrained by width and + precision but it's always known.) */ + res.knownrange = -1 < width; if (spec.modifier == FMT_LEN_l) { @@ -1626,19 +1681,32 @@ format_string (const conversion_spec &spec, tree arg) if (0 <= prec) res.range.max = prec; } - else + else if (0 <= width) { - /* The output od a "%s" directive with a constant argument - is bounded, constant, and obviously in a known range. */ + /* The output of a "%s" directive with a constant argument + and constant or no width is bounded. It is constant if + precision is either not specified or it is specified and + its value is known. */ res.bounded = true; - res.constant = true; + res.constant = prec != HOST_WIDE_INT_MIN; + } + else if (width == HOST_WIDE_INT_MIN) + { + /* Specified but unknown width makes the output unbounded. */ + res.range.max = HOST_WIDE_INT_MAX; } - if (0 <= prec && (unsigned)prec < res.range.min) + if (0 <= prec && (unsigned HOST_WIDE_INT)prec < res.range.min) { res.range.min = prec; res.range.max = prec; } + else if (prec == HOST_WIDE_INT_MIN) + { + /* When precision is specified but not known the lower + bound is assumed to be as low as zero. */ + res.range.min = 0; + } } else { @@ -1652,10 +1720,10 @@ format_string (const conversion_spec &spec, tree arg) { if (slen.range.min >= target_int_max ()) slen.range.min = 0; - else if ((unsigned)prec < slen.range.min) + else if ((unsigned HOST_WIDE_INT)prec < slen.range.min) slen.range.min = prec; - if ((unsigned)prec < slen.range.max + if ((unsigned HOST_WIDE_INT)prec < slen.range.max || slen.range.max >= target_int_max ()) slen.range.max = prec; } @@ -1678,20 +1746,23 @@ format_string (const conversion_spec &spec, tree arg) } /* Adjust the lengths for field width. */ - if (res.range.min < width) - res.range.min = width; + if (0 < width) + { + if (res.range.min < (unsigned HOST_WIDE_INT)width) + res.range.min = width; - if (res.range.max < width) - res.range.max = width; + if (res.range.max < (unsigned HOST_WIDE_INT)width) + res.range.max = width; - /* Adjust BOUNDED if width happens to make them equal. */ - if (res.range.min == res.range.max && res.range.min < target_int_max () - && bounded) - res.bounded = true; + /* Adjust BOUNDED if width happens to make them equal. */ + if (res.range.min == res.range.max && res.range.min < target_int_max () + && bounded) + res.bounded = true; + } /* When precision is specified the range of characters on output is known to be bounded by it. */ - if (-1 < prec) + if (-1 < width && -1 < prec) res.knownrange = true; return res; @@ -1807,7 +1878,7 @@ format_directive (const pass_sprintf_length::call_info &info, (int)cvtlen, cvtbeg, fmtres.range.min, navail); } - else + else if (fmtres.range.max < HOST_WIDE_INT_MAX) { const char* fmtstr = (info.bounded @@ -1821,6 +1892,19 @@ format_directive (const pass_sprintf_length::call_info &info, (int)cvtlen, cvtbeg, fmtres.range.min, fmtres.range.max, navail); } + else + { + const char* fmtstr + = (info.bounded + ? G_("%<%.*s%> directive output truncated writing " + "%wu or more bytes into a region of size %wu") + : G_("%<%.*s%> directive writing %wu or more bytes " + "into a region of size %wu")); + warned = fmtwarn (dirloc, pargrange, NULL, + OPT_Wformat_length_, fmtstr, + (int)cvtlen, cvtbeg, + fmtres.range.min, navail); + } } else if (navail < fmtres.range.max && (((spec.specifier == 's' @@ -2277,13 +2361,22 @@ pass_sprintf_length::compute_format_length (const call_info &info, if (dollar || !spec.star_width) { - if (spec.have_width && spec.width == 0) + if (spec.have_width) { - /* The '0' that has been interpreted as a width above is - actually a flag. Reset HAVE_WIDTH, set the '0' flag, - and continue processing other flags. */ - spec.have_width = false; - spec.set_flag ('0'); + if (spec.width == 0) + { + /* The '0' that has been interpreted as a width above is + actually a flag. Reset HAVE_WIDTH, set the '0' flag, + and continue processing other flags. */ + spec.have_width = false; + spec.set_flag ('0'); + } + else if (!dollar) + { + /* (Non-zero) width has been seen. The next character + is either a period or a digit. */ + goto start_precision; + } } /* When either '$' has been seen, or width has not been seen, the next field is the optional flags followed by an optional @@ -2328,6 +2421,7 @@ pass_sprintf_length::compute_format_length (const call_info &info, } } + start_precision: if ('.' == *pf) { ++pf; @@ -2345,7 +2439,12 @@ pass_sprintf_length::compute_format_length (const call_info &info, ++pf; } else - return; + { + /* The decimal precision or the asterisk are optional. + When neither is specified it's taken to be zero. */ + spec.precision = 0; + spec.have_precision = true; + } } switch (*pf) @@ -2705,9 +2804,9 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi) if (idx_dstsize == HOST_WIDE_INT_M1U) { - // For non-bounded functions like sprintf, to determine - // the size of the destination from the object or pointer - // passed to it as the first argument. + /* For non-bounded functions like sprintf, determine the size + of the destination from the object or pointer passed to it + as the first argument. */ dstsize = get_destination_size (gimple_call_arg (info.callstmt, 0)); } else if (tree size = gimple_call_arg (info.callstmt, idx_dstsize)) @@ -2719,10 +2818,18 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi) dstsize = tree_to_uhwi (size); /* No object can be larger than SIZE_MAX bytes (half the address space) on the target. This imposes a limit that's one byte - less than that. */ + less than that. + The functions are defined only for output of at most INT_MAX + bytes. Specifying a bound in excess of that limit effectively + defeats the bounds checking (and on some implementations such + as Solaris cause the function to fail with EINVAL). */ if (dstsize >= target_size_max () / 2) warning_at (gimple_location (info.callstmt), OPT_Wformat_length_, - "specified destination size %wu too large", + "specified destination size %wu is too large", + dstsize); + else if (dstsize > target_int_max ()) + warning_at (gimple_location (info.callstmt), OPT_Wformat_length_, + "specified destination size %wu exceeds %", dstsize); } else if (TREE_CODE (size) == SSA_NAME) diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c index d568f9c..cdaeeac 100644 --- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c @@ -44,6 +44,26 @@ void test_arg_int (int i, int n) for (i = -n; i != n; ++i) T (8, "%08x", i); + + /* As a special case, a precision of zero with an argument of zero + results in zero bytes (unless modified by width). */ + T (0, "%.0d", ival (0)); + T (0, "%.0i", ival (0)); + T (0, "%.0o", ival (0)); + T (0, "%.0u", ival (0)); + T (0, "%.0x", ival (0)); + + T (0, "%.*d", 0, ival (0)); + T (0, "%.*i", 0, ival (0)); + T (0, "%.*o", 0, ival (0)); + T (0, "%.*u", 0, ival (0)); + T (0, "%.*x", 0, ival (0)); + + T (1, "%1.0d", ival (0)); + T (1, "%1.0i", ival (0)); + T (1, "%1.0o", ival (0)); + T (1, "%1.0u", ival (0)); + T (1, "%1.0x", ival (0)); } void test_arg_string (const char *s) diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-6.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-6.c new file mode 100644 index 0000000..375fc09 --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-6.c @@ -0,0 +1,73 @@ +/* PR middle-end/78476 - snprintf(0, 0, ...) with known arguments not + optimized away + A negative test complementing builtin-sprintf-5.c to verify that calls + to the function that do not return a constant are not optimized away. + { dg-compile } + { dg-options "-O2 -fdump-tree-optimized" } + { dg-require-effective-target int32plus } */ + +#define CONCAT(a, b) a ## b +#define CAT(a, b) CONCAT (a, b) + +#define T(...) \ + do { \ + int CAT (n, __LINE__) = __builtin_snprintf (0, 0, __VA_ARGS__); \ + sink (CAT (n, __LINE__)); \ + } while (0) + +void sink (int); + +static int +int_range (int min, int max) +{ + extern int int_value (void); + int val = int_value (); + if (val < min || max < val) + val = min; + return val; +} + +#define R(min, max) int_range (min, max) + +void test_arg_int (int width, int prec, int i, int n) +{ + T ("%i", i); + T ("%1i", i); + T ("%2i", i); + T ("%3i", i); + T ("%4i", i); + + T ("%*i", width, 0); + T ("%*i", width, 1); + T ("%*i", width, i); + + T ("%.*i", prec, 0); + T ("%.*i", prec, 1); + T ("%.*i", prec, i); + T ("%.*i", 0, i); + + T ("%i", R (1, 10)); + + for (i = -n; i != n; ++i) + T ("%*x", n, i); +} + +void test_arg_string (int width, int prec, const char *s) +{ + T ("%-s", s); + T ("%1s", s); + T ("%.1s", s); + T ("%*s", width, s); + T ("%.*s", prec, s); + T ("%1.*s", prec, s); + T ("%*.1s", width, s); + T ("%*.*s", width, prec, s); + T ("%*s", width, "123"); + T ("%.*s", prec, "123"); + T ("%1.*s", prec, "123"); + T ("%*.1s", width, "123"); + T ("%*.*s", width, prec, "123"); +} + + +/* { dg-final { scan-tree-dump-times "snprintf" 27 "optimized"} } */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c index a24889b..4aafc9f 100644 --- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c @@ -233,6 +233,8 @@ void test_sprintf_chk_s_const (void) T ( 1, "%*s", 1, s0); /* { dg-warning "nul past the end" } */ T (-1, "%*s", 1, s0); /* No warning for unknown destination size. */ + T (1, "%.s", ""); + T (1, "%.s", "123"); T (1, "%.0s", "123"); T (1, "%.0s", s3); T (1, "%.*s", 0, "123"); @@ -450,6 +452,24 @@ void test_sprintf_chk_hh_const (void) T (4, "%hhi %hhi", 10, 1); /* { dg-warning "nul past the end" } */ T (4, "%hhi %hhi", 11, 12); /* { dg-warning "into a region" } */ + /* As a special case, a precision of zero with an argument of zero + results in zero bytes (unless modified by width). */ + T (1, "%.0hhd", 0); + T (1, "%+.0hhd", 0); + T (1, "%-.0hhd", 0); + T (1, "% .0hhd", 0); + T (1, "%0.0hhd", 0); /* { dg-warning ".0. flag ignored with precision" } */ + T (1, "%00.0hhd", 0); /* { dg-warning "repeated .0. flag in format" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%-0.0hhd", 0); /* { dg-warning ".0. flag ignored with .-. flag" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%.0hhi", 0); + T (1, "%.0hho", 0); + T (1, "%#.0hho", 0); + T (1, "%.0hhx", 0); + T (1, "%.0hhX", 0); + T (1, "%#.0hhX", 0); + T (5, "%0*hhd %0*hhi", 0, 7, 0, 9); T (5, "%0*hhd %0*hhi", 1, 7, 1, 9); T (5, "%0*hhd %0*hhi", 1, 7, 2, 9); @@ -546,14 +566,32 @@ void test_sprintf_chk_h_const (void) T (4, "%#hx", 0x100); /* { dg-warning "into a region" } */ T (4, "%#hx", -1); /* { dg-warning "into a region" } */ + /* As a special case, a precision of zero with an argument of zero + results in zero bytes (unless modified by width). */ + T (1, "%.0hd", 0); + T (1, "%+.0hd", 0); + T (1, "%-.0hd", 0); + T (1, "% .0hd", 0); + T (1, "%0.0hd", 0); /* { dg-warning ".0. flag ignored with precision" } */ + T (1, "%00.0hd", 0); /* { dg-warning "repeated .0. flag in format" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%-0.0hd", 0); /* { dg-warning ".0. flag ignored with .-. flag" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%.0hi", 0); + T (1, "%.0ho", 0); + T (1, "%#.0ho", 0); + T (1, "%.0hx", 0); + T (1, "%.0hX", 0); + T (1, "%#.0hX", 0); + #undef MAX #define MAX 65535 - T (1, "%hhu", 0); /* { dg-warning "nul past the end" } */ - T (1, "%hhu", 1); /* { dg-warning "nul past the end" } */ - T (1, "%hhu", -1); /* { dg-warning "into a region" } */ - T (1, "%hhu", MAX); /* { dg-warning "into a region" } */ - T (1, "%hhu", MAX + 1); /* { dg-warning "nul past the end" } */ + T (1, "%hu", 0); /* { dg-warning "nul past the end" } */ + T (1, "%hu", 1); /* { dg-warning "nul past the end" } */ + T (1, "%hu", -1); /* { dg-warning "into a region" } */ + T (1, "%hu", MAX); /* { dg-warning "into a region" } */ + T (1, "%hu", MAX + 1); /* { dg-warning "nul past the end" } */ } /* Exercise the "%d", "%i", "%o", "%u", and "%x" directives with @@ -611,6 +649,24 @@ void test_sprintf_chk_integer_const (void) T ( 8, "%8u", 1); /* { dg-warning "nul past the end" } */ T ( 9, "%8u", 1); + /* As a special case, a precision of zero with an argument of zero + results in zero bytes (unless modified by width). */ + T (1, "%.0d", 0); + T (1, "%+.0d", 0); + T (1, "%-.0d", 0); + T (1, "% .0d", 0); + T (1, "%0.0d", 0); /* { dg-warning ".0. flag ignored with precision" } */ + T (1, "%00.0d", 0); /* { dg-warning "repeated .0. flag in format" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%-0.0d", 0); /* { dg-warning ".0. flag ignored with .-. flag" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%.0i", 0); + T (1, "%.0o", 0); + T (1, "%#.0o", 0); + T (1, "%.0x", 0); + T (1, "%.0X", 0); + T (1, "%#.0X", 0); + T ( 7, "%1$i%2$i%3$i", 1, 23, 456); T ( 8, "%1$i%2$i%3$i%1$i", 1, 23, 456); T ( 8, "%1$i%2$i%3$i%2$i", 1, 23, 456); /* { dg-warning "nul past the end" } */ @@ -691,6 +747,24 @@ void test_sprintf_chk_j_const (void) T ( 8, "%8ju", I (1)); /* { dg-warning "nul past the end" } */ T ( 9, "%8ju", I (1)); + + /* As a special case, a precision of zero with an argument of zero + results in zero bytes (unless modified by width). */ + T (1, "%.0jd", I (0)); + T (1, "%+.0jd", I (0)); + T (1, "%-.0jd", I (0)); + T (1, "% .0jd", I (0)); + T (1, "%0.0jd", I (0)); /* { dg-warning ".0. flag ignored with precision" } */ + T (1, "%00.0jd", I (0)); /* { dg-warning "repeated .0. flag in format" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%-0.0jd", I (0)); /* { dg-warning ".0. flag ignored with .-. flag" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%.0ji", I (0)); + T (1, "%.0jo", I (0)); + T (1, "%#.0jo", I (0)); + T (1, "%.0jx", I (0)); + T (1, "%.0jX", I (0)); + T (1, "%#.0jX", I (0)); } /* Exercise the "%ld", "%li", "%lo", "%lu", and "%lx" directives @@ -747,6 +821,24 @@ void test_sprintf_chk_l_const (void) T ( 8, "%8lu", 1L); /* { dg-warning "nul past the end" } */ T ( 9, "%8lu", 1L); + + /* As a special case, a precision of zero with an argument of zero + results in zero bytes (unless modified by width). */ + T (1, "%.0ld", 0L); + T (1, "%+.0ld", 0L); + T (1, "%-.0ld", 0L); + T (1, "% .0ld", 0L); + T (1, "%0.0ld", 0L); /* { dg-warning ".0. flag ignored with precision" } */ + T (1, "%00.0ld", 0L); /* { dg-warning "repeated .0. flag in format" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%-0.0ld", 0L); /* { dg-warning ".0. flag ignored with .-. flag" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%.0li", 0L); + T (1, "%.0lo", 0L); + T (1, "%#.0lo", 0L); + T (1, "%.0lx", 0L); + T (1, "%.0lX", 0L); + T (1, "%#.0lX", 0L); } /* Exercise the "%lld", "%lli", "%llo", "%llu", and "%llx" directives @@ -858,37 +950,56 @@ void test_sprintf_chk_z_const (void) void test_sprintf_chk_a_const (void) { - T (-1, "%a", 0.0); - T (-1, "%la", 0.0); + T (-1, "%a", 0.0); + T (-1, "%la", 0.0); + T (-1, "%.a", 0.0); + T (-1, "%.la", 0.0); + T (-1, "%123.a", 0.0); + T (-1, "%234.la", 0.0); + T (-1, "%.345a", 0.0); + T (-1, "%456.567la", 0.0); /* The least number of bytes on output is 6 for "0x0p+0". When precision is missing the number of digits after the decimal point isn't fully specified by C (it seems like a defect). */ - T (0, "%a", 0.0); /* { dg-warning "into a region" } */ - T (0, "%la", 0.0); /* { dg-warning "into a region" } */ - T (1, "%a", 0.0); /* { dg-warning "into a region" } */ - T (2, "%a", 0.0); /* { dg-warning "into a region" } */ - T (3, "%a", 0.0); /* { dg-warning "into a region" } */ - T (4, "%a", 0.0); /* { dg-warning "into a region" } */ - T (5, "%a", 0.0); /* { dg-warning "into a region" } */ - T (6, "%a", 0.0); /* { dg-warning "writing a terminating nul" } */ + T (0, "%a", 0.0); /* { dg-warning "into a region" } */ + T (0, "%la", 0.0); /* { dg-warning "into a region" } */ + T (1, "%a", 0.0); /* { dg-warning "into a region" } */ + T (2, "%a", 0.0); /* { dg-warning "into a region" } */ + T (3, "%a", 0.0); /* { dg-warning "into a region" } */ + T (4, "%a", 0.0); /* { dg-warning "into a region" } */ + T (5, "%a", 0.0); /* { dg-warning "into a region" } */ + T (6, "%a", 0.0); /* { dg-warning "writing a terminating nul" } */ T (7, "%a", 0.0); - T (0, "%.0a", 0.0); /* { dg-warning "into a region" } */ - T (0, "%.0la", 0.0); /* { dg-warning "into a region" } */ - T (1, "%.0a", 0.0); /* { dg-warning "into a region" } */ - T (2, "%.0a", 0.0); /* { dg-warning "into a region" } */ - T (3, "%.0a", 0.0); /* { dg-warning "into a region" } */ - T (4, "%.0a", 0.0); /* { dg-warning "into a region" } */ - T (5, "%.0a", 0.0); /* { dg-warning "into a region" } */ - T (6, "%.0a", 0.0); /* { dg-warning "writing a terminating nul" } */ + T (0, "%.a", 0.0); /* { dg-warning "into a region" } */ + T (0, "%.0a", 0.0); /* { dg-warning "into a region" } */ + T (0, "%.0la", 0.0); /* { dg-warning "into a region" } */ + T (1, "%.0a", 0.0); /* { dg-warning "into a region" } */ + T (2, "%.0a", 0.0); /* { dg-warning "into a region" } */ + T (3, "%.0a", 0.0); /* { dg-warning "into a region" } */ + T (4, "%.0a", 0.0); /* { dg-warning "into a region" } */ + T (5, "%.0a", 0.0); /* { dg-warning "into a region" } */ + T (6, "%.0a", 0.0); /* { dg-warning "writing a terminating nul" } */ + + T (7, "%6.a", 0.0); + T (7, "%7.a", 0.0); /* { dg-warning "writing a terminating nul" } */ + T (7, "%7.1a", 0.0); /* { dg-warning "writing 8 bytes into a region of size 7" } */ + + T (7, "%.a", 0.0); T (7, "%.0a", 0.0); } void test_sprintf_chk_e_const (void) { - T (-1, "%E", 0.0); - T (-1, "%lE", 0.0); + T (-1, "%E", 0.0); + T (-1, "%lE", 0.0); + T (-1, "%.E", 0.0); + T (-1, "%.lE", 0.0); + T (-1, "%123.E", 0.0); + T (-1, "%234.lE", 0.0); + T (-1, "%.345E", 0.0); + T (-1, "%.456lE", 0.0); T ( 0, "%E", 0.0); /* { dg-warning "into a region" } */ T ( 0, "%e", 0.0); /* { dg-warning "into a region" } */ @@ -910,8 +1021,10 @@ void test_sprintf_chk_e_const (void) T (16, "%.8e", -1.9e+104); /* { dg-warning "nul past the end" } */ T (17, "%.8e", -2.0e+105); /* -2.00000000e+105 */ + T ( 5, "%.e", 0.0); /* { dg-warning "nul past the end" } */ T ( 5, "%.0e", 0.0); /* { dg-warning "nul past the end" } */ T ( 5, "%.0e", 1.0); /* { dg-warning "nul past the end" } */ + T ( 6, "%.e", 1.0); T ( 6, "%.0e", 1.0); /* The actual output of the following directives depends on the rounding @@ -938,7 +1051,7 @@ void test_sprintf_chk_e_const (void) the value one, and unknown strings are assumed to have a zero length. */ -void test_sprintf_chk_s_nonconst (int i, const char *s) +void test_sprintf_chk_s_nonconst (int w, int p, const char *s) { T (-1, "%s", s); T ( 0, "%s", s); /* { dg-warning "nul past the end" } */ @@ -946,6 +1059,19 @@ void test_sprintf_chk_s_nonconst (int i, const char *s) T ( 1, "%.0s", s); T ( 1, "%.1s", s); /* { dg-warning "nul past the end" } */ + /* The string argument is constant but the width and/or precision + is not. */ + T ( 1, "%*s", w, ""); + T ( 1, "%*s", w, "1"); /* { dg-warning "nul past the end" } */ + T ( 1, "%.*s", w, ""); + T ( 1, "%.*s", w, "1"); /* { dg-warning "may write a terminating nul" } */ + T ( 1, "%.*s", w, "123"); /* { dg-warning "writing between 0 and 3 bytes into a region of size 1" } */ + + T ( 1, "%*s", w, "123"); /* { dg-warning "writing 3 or more bytes into a region of size 1" } */ + T ( 2, "%*s", w, "123"); /* { dg-warning "writing 3 or more bytes into a region of size 2" } */ + T ( 3, "%*s", w, "123"); /* { dg-warning "writing a terminating nul past the end" } */ + T ( 4, "%*s", w, "123"); + /* The following will definitely write past the end of the buffer, but since at level 1 the length of an unknown string argument is assumed to be zero, it will write the terminating nul past @@ -957,7 +1083,7 @@ void test_sprintf_chk_s_nonconst (int i, const char *s) /* Exercise the hh length modifier with all integer specifiers and a non-constant argument. */ -void test_sprintf_chk_hh_nonconst (int a) +void test_sprintf_chk_hh_nonconst (int w, int p, int a) { T (-1, "%hhd", a); @@ -999,11 +1125,48 @@ void test_sprintf_chk_hh_nonconst (int a) T (2, "%#hho", a); /* { dg-warning "nul past the end" } */ T (2, "%#hhx", a); /* { dg-warning ".%#hhx. directive writing between 3 and . bytes into a region of size 2" } */ + T (3, "%0hhd", a); + T (3, "%1hhd", a); T (3, "%2hhd", a); T (3, "%2hhi", a); T (3, "%2hho", a); T (3, "%2hhu", a); T (3, "%2hhx", a); + T (3, "%2.hhx", a); + + T (3, "%3hhd", a); /* { dg-warning "nul past the end" } */ + T (3, "%3hhi", a); /* { dg-warning "nul past the end" } */ + T (3, "%3hho", a); /* { dg-warning "nul past the end" } */ + T (3, "%3hhu", a); /* { dg-warning "nul past the end" } */ + T (3, "%3hhx", a); /* { dg-warning "nul past the end" } */ + T (3, "%3.hhx", a); /* { dg-warning "nul past the end" } */ + + T (4, "%5hhd", a); /* { dg-warning "into a region" } */ + T (4, "%6hhi", a); /* { dg-warning "into a region" } */ + T (4, "%7hho", a); /* { dg-warning "into a region" } */ + T (4, "%8hhu", a); /* { dg-warning "into a region" } */ + T (4, "%9hhx", a); /* { dg-warning "into a region" } */ + + T (3, "%.hhd", a); + T (3, "%.0hhd", a); + T (3, "%.1hhd", a); + T (3, "%.2hhd", a); + T (3, "%.2hhi", a); + T (3, "%.2hho", a); + T (3, "%.2hhu", a); + T (3, "%.2hhx", a); + + T (3, "%.3hhd", a); /* { dg-warning "nul past the end" } */ + T (3, "%.3hhi", a); /* { dg-warning "nul past the end" } */ + T (3, "%.3hho", a); /* { dg-warning "nul past the end" } */ + T (3, "%.3hhu", a); /* { dg-warning "nul past the end" } */ + T (3, "%.3hhx", a); /* { dg-warning "nul past the end" } */ + + T (4, "%.5hhd", a); /* { dg-warning "into a region" } */ + T (4, "%.6hhi", a); /* { dg-warning "into a region" } */ + T (4, "%.7hho", a); /* { dg-warning "into a region" } */ + T (4, "%.8hhu", a); /* { dg-warning "into a region" } */ + T (4, "%.9hhx", a); /* { dg-warning "into a region" } */ /* Exercise cases where the type of the actual argument (whose value and range are unknown) constrain the size of the output and so @@ -1012,6 +1175,55 @@ void test_sprintf_chk_hh_nonconst (int a) T (2, "%hhd", (UChar)a); T (2, "%hhi", (UChar)a); T (2, "%-hhi", (UChar)a); + + /* Exercise cases where the argument is known but width isn't. */ + T (0, "%*hhi", w, 0); /* { dg-warning "into a region" } */ + T (1, "%*hhi", w, 0); /* { dg-warning "nul past the end" } */ + T (2, "%*hhi", w, 0); + T (2, "%*hhi", w, 12); /* { dg-warning "nul past the end" } */ + T (2, "%*hhi", w, 123); /* { dg-warning "into a region" } */ + + /* The argument is known but precision isn't. When the argument + is zero only the first call can be diagnosed since a zero + precision would result in no bytes on output. */ + T (0, "%.*hhi", p, 0); /* { dg-warning "nul past the end" } */ + T (1, "%.*hhi", p, 0); + T (2, "%.*hhi", p, 0); + T (2, "%.*hhi", p, 12); /* { dg-warning "nul past the end" } */ + T (2, "%.*hhi", p, 123); /* { dg-warning "into a region" } */ + + /* The argument is known but neither width nor precision is. */ + T (0, "%*.*hhi", w, p, 0); /* { dg-warning "nul past the end" } */ + T (1, "%*.*hhi", w, p, 0); + T (2, "%*.*hhi", w, p, 0); + T (2, "%*.*hhi", w, p, 12); /* { dg-warning "nul past the end" } */ + T (2, "%*.*hhi", w, p, 123); /* { dg-warning "into a region" } */ + + /* The argument and width are known but precision isn't. */ + T (0, "%1.*hhi", p, 0); /* { dg-warning "into a region" } */ + T (0, "%-1.*hhi", p, 0); /* { dg-warning "into a region" } */ + T (1, "%1.*hhi", p, 0); /* { dg-warning "nul past the end" } */ + T (2, "%1.*hhi", p, 0); + T (2, "%2.*hhi", p, 0); /* { dg-warning "nul past the end" } */ + T (2, "%1.*hhi", p, 12); /* { dg-warning "nul past the end" } */ + T (2, "%2.*hhi", p, 12); /* { dg-warning "nul past the end" } */ + + T (2, "%1.*hhi", p, 123); /* { dg-warning "into a region" } */ + T (2, "%2.*hhi", p, 123); /* { dg-warning "into a region" } */ + T (2, "%3.*hhi", p, 123); /* { dg-warning "into a region" } */ + + /* The argument and precision are known but width isn't. */ + T (0, "%*.1hhi", w, 0); /* { dg-warning "into a region" } */ + T (1, "%*.1hhi", w, 0); /* { dg-warning "nul past the end" } */ + T (2, "%*.1hhi", w, 0); + T (2, "%*.2hhi", w, 0); /* { dg-warning "nul past the end" } */ + T (2, "%*.1hhi", w, 12); /* { dg-warning "nul past the end" } */ + T (2, "%*.2hhi", w, 12); /* { dg-warning "nul past the end" } */ + T (2, "%*.3hhi", w, 12); /* { dg-warning "into a region" } */ + + T (2, "%*.1hhi", w, 123); /* { dg-warning "into a region" } */ + T (2, "%*.2hhi", w, 123); /* { dg-warning "into a region" } */ + T (2, "%*.3hhi", w, 123); /* { dg-warning "into a region" } */ } /* Exercise the h length modifier with all integer specifiers and @@ -1063,7 +1275,7 @@ void test_sprintf_chk_h_nonconst (int a) /* Exercise all integer specifiers with no modifier and a non-constant argument. */ -void test_sprintf_chk_int_nonconst (int a) +void test_sprintf_chk_int_nonconst (int w, int p, int a) { T (-1, "%d", a); @@ -1104,12 +1316,22 @@ void test_sprintf_chk_int_nonconst (int a) T (3, "%2o", a); T (3, "%2u", a); T (3, "%2x", a); + + T (1, "%.*d", p, a); } -void test_sprintf_chk_e_nonconst (double d) +void test_sprintf_chk_e_nonconst (int w, int p, double d) { - T (-1, "%E", d); - T (-1, "%lE", d); + T (-1, "%E", d); + T (-1, "%lE", d); + T (-1, "%.E", d); + T (-1, "%.lE", d); + T (-1, "%*E", w, d); + T (-1, "%*lE", w, d); + T (-1, "%.*E", p, d); + T (-1, "%.*lE", p, d); + T (-1, "%*.*E", w, p, d); + T (-1, "%*.*lE", w, p, d); T ( 0, "%E", d); /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */ T ( 0, "%e", d); /* { dg-warning "into a region" } */ @@ -1123,9 +1345,9 @@ void test_sprintf_chk_e_nonconst (double d) T (14, "%E", d); T (14, "%e", d); - T (0, "%+E", d); /* { dg-warning "writing between 13 and 14 bytes into a region of size 0" } */ - T (0, "%-e", d); /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */ - T (0, "% E", d); /* { dg-warning "writing between 13 and 14 bytes into a region of size 0" } */ + T ( 0, "%+E", d); /* { dg-warning "writing between 13 and 14 bytes into a region of size 0" } */ + T ( 0, "%-e", d); /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */ + T ( 0, "% E", d); /* { dg-warning "writing between 13 and 14 bytes into a region of size 0" } */ /* The range of output of "%.0e" is between 5 and 7 bytes (not counting the terminating NUL. */ @@ -1136,6 +1358,9 @@ void test_sprintf_chk_e_nonconst (double d) the terminating NUL. */ T ( 7, "%.1e", d); /* { dg-warning "writing a terminating nul past the end" } */ T ( 8, "%.1e", d); + + T ( 0, "%*e", 0, d); /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */ + T ( 0, "%*e", w, d); /* { dg-warning "writing 12 or more bytes into a region of size 0" } */ } void test_sprintf_chk_f_nonconst (double d) @@ -1204,7 +1429,6 @@ void test_vsprintf_chk_c (__builtin_va_list va) /* Here in the best case each argument will format as single character, causing the terminating NUL to be written past the end. */ T (3, "%lc%c%c"); - } void test_vsprintf_chk_int (__builtin_va_list va) @@ -1254,9 +1478,11 @@ void test_vsprintf_chk_int (__builtin_va_list va) #define T(size, fmt, ...) \ __builtin_snprintf (buffer (size), objsize (size), fmt, __VA_ARGS__) -void test_snprintf_c_const (void) +void test_snprintf_c_const (char *d) { - T (-1, "%c", 0); /* { dg-warning "specified destination size \[0-9\]+ too large" } */ + T (-1, "%c", 0); /* { dg-warning "specified destination size \[0-9\]+ is too large" } */ + + __builtin_snprintf (d, INT_MAX, "%c", 0); /* Verify the full text of the diagnostic for just the distinct messages and use abbreviations in subsequent test cases. */ @@ -1306,7 +1532,7 @@ void test_snprintf_chk_c_const (void) the function by __builtin_object_size) is diagnosed. */ __builtin___snprintf_chk (buffer, 3, 0, 2, " "); /* { dg-warning "always overflow|specified size 3 exceeds the size 2 of the destination" } */ - T (-1, "%c", 0); /* { dg-warning "specified destination size \[^ \]* too large" } */ + T (-1, "%c", 0); /* { dg-warning "specified destination size \[^ \]* is too large" } */ T (0, "%c", 0); T (0, "%c%c", 0, 0); @@ -1417,7 +1643,7 @@ void test_vsprintf_int (__builtin_va_list va) void test_vsnprintf_s (__builtin_va_list va) { - T (-1, "%s"); /* { dg-warning "specified destination size \[^ \]* too large" } */ + T (-1, "%s"); /* { dg-warning "specified destination size \[^ \]* is too large" } */ T (0, "%s"); T (1, "%s"); @@ -1442,7 +1668,7 @@ void test_vsnprintf_chk_s (__builtin_va_list va) the function by __builtin_object_size) is diagnosed. */ __builtin___vsnprintf_chk (buffer, 123, 0, 122, "%-s", va); /* { dg-warning "always overflow|specified size 123 exceeds the size 122 of the destination object" } */ - __builtin___vsnprintf_chk (buffer, __SIZE_MAX__, 0, 2, "%-s", va); /* { dg-warning "always overflow|destination size .\[0-9\]+. too large" } */ + __builtin___vsnprintf_chk (buffer, __SIZE_MAX__, 0, 2, "%-s", va); /* { dg-warning "always overflow|destination size .\[0-9\]+. is too large" } */ T (0, "%s"); T (1, "%s"); diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c index 8d97fa8..f4550ba 100644 --- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c @@ -1,6 +1,8 @@ /* { dg-do compile } */ /* { dg-options "-std=c99 -O2 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */ +typedef __SIZE_TYPE__ size_t; + #ifndef LINE # define LINE 0 #endif @@ -232,3 +234,48 @@ void test_sprintf_chk_range_sshort (signed short *a, signed short *b) T ( 4, "%i", Ra (998, 999)); T ( 4, "%i", Ra (999, 1000)); /* { dg-warning "may write a terminating nul past the end of the destination" } */ } + +/* Verify that destination size in excess of INT_MAX (and, separately, + in excess of the largest object) is diagnosed. The former because + the functions are defined only for output of at most INT_MAX and + specifying a large upper bound defeats the bounds checking (and, + on some implementations such as Solaris, causes the function to + fail. The latter because due to the limit of ptrdiff_t no object + can be larger than PTRDIFF_MAX bytes. */ + +void test_too_large (char *d, int x, __builtin_va_list va) +{ + const size_t imax = __INT_MAX__; + const size_t imax_p1 = imax + 1; + + __builtin_snprintf (d, imax, "%c", x); + __builtin_snprintf (d, imax_p1, "%c", x); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "" { target lp64 } } */ + /* { dg-warning "specified destination size \[0-9\]+ is too large" "" { target { ilp32 } } .-1 } */ + + __builtin_vsnprintf (d, imax, "%c", va); + __builtin_vsnprintf (d, imax_p1, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." { target lp64 } } */ + /* { dg-warning "specified destination size \[0-9\]+ is too large" "" { target { ilp32 } } .-1 } */ + + __builtin___snprintf_chk (d, imax, 0, imax, "%c", x); + __builtin___snprintf_chk (d, imax_p1, 0, imax_p1, "%c", x); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." { target lp64 } } */ + /* { dg-warning "specified destination size \[0-9\]+ is too large" "" { target { ilp32 } } .-1 } */ + + __builtin___vsnprintf_chk (d, imax, 0, imax, "%c", va); + __builtin___vsnprintf_chk (d, imax_p1, 0, imax_p1, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." { target lp64 } } */ + /* { dg-warning "specified destination size \[0-9\]+ is too large" "" { target { ilp32 } } .-1 } */ + + const size_t ptrmax = __PTRDIFF_MAX__; + const size_t ptrmax_m1 = ptrmax - 1; + + __builtin_snprintf (d, ptrmax_m1, "%c", x); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "" { target lp64 } } */ + __builtin_snprintf (d, ptrmax, " %c", x); /* { dg-warning "specified destination size \[0-9\]+ is too large" } */ + + __builtin_vsnprintf (d, ptrmax_m1, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "" { target lp64 } } */ + __builtin_vsnprintf (d, ptrmax, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ is too large" } */ + + __builtin___snprintf_chk (d, ptrmax_m1, 0, ptrmax_m1, "%c", x); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "" { target lp64 } } */ + __builtin___snprintf_chk (d, ptrmax, 0, ptrmax, "%c", x); /* { dg-warning "specified destination size \[0-9\]+ is too large" } */ + + __builtin___vsnprintf_chk (d, ptrmax_m1, 0, ptrmax_m1, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "" { target lp64 } } */ + __builtin___vsnprintf_chk (d, ptrmax, 0, ptrmax, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ is too large" } */ +}