In both functions in the following test case the %3u argument to snprintf is in the range [0, 999] and so the directive writes exactly 3 bytes into the destination buffer of size 4. As the VRP dump shows, GCC exposes that range to the -Wformat-length checker via the get_range_info() function in the call in function f() allowing it to avoid a warning. But in the call in function g(), even though the same range is also known, it's not made available for the argument to the directive, resulting in a false positive. $ cat t.c && gcc -O2 -S -Wall -Wformat-length=2 -fdump-tree-vrp=/dev/stdout t.c void f (unsigned j, char *p) { if (j > 999) j = 0; __builtin_snprintf (p, 4, "%3u", j); } void g (unsigned j, char *p) { if (j > 999) return; __builtin_snprintf (p, 4, "%3u", j); } ;; Function f (f, funcdef_no=0, decl_uid=1796, cgraph_uid=0, symbol_order=0) ;; 1 loops found ;; ;; Loop 0 ;; header 0, latch 1 ;; depth 0, outer -1 ;; nodes: 0 1 2 3 4 ;; 2 succs { 3 4 } ;; 3 succs { 4 } ;; 4 succs { 1 } SSA replacement table N_i -> { O_1 ... O_j } means that N_i replaces O_1, ..., O_j j_6 -> { j_2(D) } j_7 -> { j_2(D) } Incremental SSA update started at block: 2 Number of blocks in CFG: 6 Number of blocks to update: 4 ( 67%) Value ranges after VRP: j_1: [0, 999] EQUIVALENCES: { } (0 elements) j_2(D): VARYING j_6: [1000, +INF] EQUIVALENCES: { j_2(D) } (1 elements) j_7: [0, 999] EQUIVALENCES: { j_2(D) } (1 elements) Removing basic block 3 f (unsigned int j, char * p) { <bb 2> [100.00%]: if (j_2(D) > 999) goto <bb 4>; [54.00%] else goto <bb 3>; [46.00%] <bb 3> [46.00%]: <bb 4> [100.00%]: # j_1 = PHI <j_2(D)(3), 0(2)> __builtin_snprintf (p_4(D), 4, "%3u", j_1); return; } ;; Function f (f, funcdef_no=0, decl_uid=1796, cgraph_uid=0, symbol_order=0) ;; 1 loops found ;; ;; Loop 0 ;; header 0, latch 1 ;; depth 0, outer -1 ;; nodes: 0 1 2 3 4 ;; 2 succs { 3 4 } ;; 3 succs { 4 } ;; 4 succs { 1 } SSA replacement table N_i -> { O_1 ... O_j } means that N_i replaces O_1, ..., O_j j_6 -> { j_2(D) } j_7 -> { j_2(D) } Incremental SSA update started at block: 2 Number of blocks in CFG: 6 Number of blocks to update: 4 ( 67%) Value ranges after VRP: j_1: [0, 999] EQUIVALENCES: { } (0 elements) j_2(D): VARYING j_6: [1000, +INF] EQUIVALENCES: { j_2(D) } (1 elements) j_7: [0, 999] EQUIVALENCES: { j_2(D) } (1 elements) Removing basic block 3 f (unsigned int j, char * p) { <bb 2> [100.00%]: if (j_2(D) > 999) goto <bb 4>; [54.00%] else goto <bb 3>; [46.00%] <bb 3> [46.00%]: <bb 4> [100.00%]: # j_1 = PHI <j_2(D)(3), 0(2)> __builtin_snprintf (p_4(D), 4, "%3u", j_1); return; } ;; Function g (g, funcdef_no=1, decl_uid=1800, cgraph_uid=1, symbol_order=1) ;; 1 loops found ;; ;; Loop 0 ;; header 0, latch 1 ;; depth 0, outer -1 ;; nodes: 0 1 2 3 4 ;; 2 succs { 4 3 } ;; 3 succs { 4 } ;; 4 succs { 1 } SSA replacement table N_i -> { O_1 ... O_j } means that N_i replaces O_1, ..., O_j j_6 -> { j_2(D) } Incremental SSA update started at block: 2 Number of blocks in CFG: 5 Number of blocks to update: 2 ( 40%) Value ranges after VRP: .MEM_1: VARYING j_2(D): VARYING j_6: [0, 999] EQUIVALENCES: { j_2(D) } (1 elements) g (unsigned int j, char * p) { <bb 2> [100.00%]: if (j_2(D) > 999) goto <bb 4>; [51.01%] else goto <bb 3>; [48.99%] <bb 3> [48.99%]: __builtin_snprintf (p_4(D), 4, "%3u", j_2(D)); <bb 4> [100.00%]: return; } t.c: In function ‘g’: t.c:14:30: warning: ‘%3u’ directive output may be truncated writing between 3 and 10 bytes into a region of size 4 [-Wformat-length=] __builtin_snprintf (p, 4, "%3u", j); ^~~ t.c:14:29: note: using the range [1, 4294967295] for directive argument __builtin_snprintf (p, 4, "%3u", j); ^~~~~ t.c:14:3: note: format output between 4 and 11 bytes into a destination of size 4 __builtin_snprintf (p, 4, "%3u", j); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ;; Function g (g, funcdef_no=1, decl_uid=1800, cgraph_uid=1, symbol_order=1) ;; 1 loops found ;; ;; Loop 0 ;; header 0, latch 1 ;; depth 0, outer -1 ;; nodes: 0 1 2 3 4 ;; 2 succs { 4 3 } ;; 3 succs { 4 } ;; 4 succs { 1 } SSA replacement table N_i -> { O_1 ... O_j } means that N_i replaces O_1, ..., O_j j_6 -> { j_2(D) } Incremental SSA update started at block: 2 Number of blocks in CFG: 5 Number of blocks to update: 2 ( 40%) Value ranges after VRP: .MEM_1: VARYING j_2(D): VARYING j_6: [0, 999] EQUIVALENCES: { j_2(D) } (1 elements) g (unsigned int j, char * p) { <bb 2> [100.00%]: if (j_2(D) > 999) goto <bb 4>; [51.01%] else goto <bb 3>; [48.99%] <bb 3> [48.99%]: __builtin_snprintf (p_4(D), 4, "%3u", j_2(D)); <bb 4> [100.00%]: return; }
The same underlying problem (lack of range info) can be seen in the VRP dump for the following test case. The -Walloca-larger-than option is interesting because the alloca pass that implements it tries to compensate for the missing range info by deriving it from conditions the alloca() argument is subjected to (see the alloca_call_type_by_arg function). Although the logic it uses is quite simple in this case it manages to successfully determine the range on its own and avoids the warning. $ cat t.c && gcc -O2 -S -Wall -Walloca-larger-than=255 -fdump-tree-vrp=/dev/stdout t.c void foo (void*); void f (unsigned long j) { if (j / 256) return; foo (__builtin_alloca (j)); } ;; Function f (f, funcdef_no=0, decl_uid=1797, cgraph_uid=0, symbol_order=0) ;; 1 loops found ;; ;; Loop 0 ;; header 0, latch 1 ;; depth 0, outer -1 ;; nodes: 0 1 2 3 4 ;; 2 succs { 4 3 } ;; 3 succs { 4 } ;; 4 succs { 1 } SSA replacement table N_i -> { O_1 ... O_j } means that N_i replaces O_1, ..., O_j j_7 -> { j_3(D) } Incremental SSA update started at block: 2 Number of blocks in CFG: 5 Number of blocks to update: 2 ( 40%) Value ranges after VRP: _1: ~[0B, 0B] .MEM_2: VARYING j_3(D): VARYING j_7: [0, 255] EQUIVALENCES: { j_3(D) } (1 elements) f (long unsigned int j) { void * _1; <bb 2> [100.00%]: if (j_3(D) > 255) goto <bb 4>; [51.01%] else goto <bb 3>; [48.99%] <bb 3> [48.99%]: _1 = __builtin_alloca (j_3(D)); foo (_1); <bb 4> [100.00%]: return; } ;; Function f (f, funcdef_no=0, decl_uid=1797, cgraph_uid=0, symbol_order=0) ;; 1 loops found ;; ;; Loop 0 ;; header 0, latch 1 ;; depth 0, outer -1 ;; nodes: 0 1 2 3 4 ;; 2 succs { 4 3 } ;; 3 succs { 4 } ;; 4 succs { 1 } SSA replacement table N_i -> { O_1 ... O_j } means that N_i replaces O_1, ..., O_j j_7 -> { j_3(D) } Incremental SSA update started at block: 2 Number of blocks in CFG: 5 Number of blocks to update: 2 ( 40%) Value ranges after VRP: _1: ~[0B, 0B] .MEM_2: VARYING j_3(D): VARYING j_7: [0, 255] EQUIVALENCES: { j_3(D) } (1 elements) f (long unsigned int j) { void * _1; <bb 2> [100.00%]: if (j_3(D) > 255) goto <bb 4>; [51.01%] else goto <bb 3>; [48.99%] <bb 3> [48.99%]: _1 = __builtin_alloca (j_3(D)); foo (_1); <bb 4> [100.00%]: return; }
I think there might be a dup of this bug already. basically VRP does a copy prop of where the assert was. get_range_info is not position sensitive so the range is gone after VRP.
Author: msebor Date: Sun Jan 8 23:42:09 2017 New Revision: 244210 URL: https://gcc.gnu.org/viewcvs?rev=244210&root=gcc&view=rev Log: PR tree-optimization/78913 - Probably misleading error reported by -Wformat-length PR middle-end/77708 - -Wformat-length %s warns for snprintf gcc/ChangeLog: PR middle-end/77708 * doc/invoke.texi (Warning Options): Document -Wformat-truncation. * gimple-ssa-sprintf.c (call_info::reval_used, call_info::warnopt): New member functions. (format_directive): Used them. (add_bytes): Same. (pass_sprintf_length::handle_gimple_call): Same. * graphite-sese-to-poly.c (tree_int_to_gmp): Increase buffer size to avoid truncation for any argument. (extract_affine_mul): Same. * tree.c (get_file_function_name): Same. gcc/c-family/ChangeLog: PR middle-end/77708 * c.opt (-Wformat-truncation): New option. gcc/fortran/ChangeLog: PR tree-optimization/78913 PR middle-end/77708 * trans-common.c (build_equiv_decl): Increase buffer size to avoid truncation for any argument. * trans-types.c (gfc_build_logical_type): Same. gcc/testsuite/ChangeLog: PR middle-end/77708 * gcc.dg/tree-ssa/builtin-snprintf-warn-1.c: New test. * gcc.dg/tree-ssa/builtin-snprintf-warn-2.c: New test. * gcc.dg/tree-ssa/builtin-sprintf-warn-6.c: XFAIL test cases failing due to bug 78969. * gcc.dg/format/pr78569.c: Adjust. Added: trunk/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-warn-1.c trunk/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-warn-2.c Modified: trunk/gcc/ChangeLog trunk/gcc/c-family/ChangeLog trunk/gcc/c-family/c.opt trunk/gcc/doc/invoke.texi trunk/gcc/fortran/ChangeLog trunk/gcc/fortran/trans-common.c trunk/gcc/fortran/trans-types.c trunk/gcc/gimple-ssa-sprintf.c trunk/gcc/graphite-sese-to-poly.c trunk/gcc/testsuite/ChangeLog trunk/gcc/testsuite/gcc.dg/format/pr78569.c trunk/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-6.c trunk/gcc/tree.c
Found similar false positive on lxc project. Original snippet of code: https://github.com/lxc/lxc/blob/5059aae90584d7d80b3494088920da4ba73e2b2a/src/lxc/cgroups/cgfsng.c#L1379-L1395 Simplified version: $ cat a.c #include <stdio.h> void f(char * p /* NNN\0" */) { for (int idx = 0; idx < 1000; idx++) { // guaranteed to be in [0-999] range snprintf (p, 4, "%d", idx); } } $ gcc -O2 -c a.c -Wall a.c: In function 'f': a.c:6:25: warning: '__builtin___snprintf_chk' output may be truncated before the last format character [-Wformat-truncation=] snprintf (p, 4, "%d", idx); ^~~~ /usr/include/bits/stdio2.h:64:10: note: '__builtin___snprintf_chk' output between 2 and 5 bytes into a destination of size 4 If I change 1000 to 999 for (int idx = 0; idx < 999; idx++) { no warning will be issued. Looks like what happens here is that gcc does not distinct between idx in the for loop itself that has range of [0-999] and idx outside for loop, which has value range of [1000-1000].
(In reply to Sergei Trofimovich from comment #4) > Found similar false positive on lxc project. Thanks for the test case. The VRP pass computes the correct range information for the argument but the range made available outside it via the get_range_info() function is that of idx_6, not idx_10 below (compile with -fdump-tree-vrp=/dev/stdout to see the output). It's possible that this problem is caused by the same underlying limitation as the one in comment #0. Let me confirm this bug on that basis. Value ranges after VRP: idx_1: [1, 999] EQUIVALENCES: { idx_6 } (1 elements) idx_6: [1, 1000] idx_10: [0, 999] .MEM_11: VARYING Removing basic block 5 f (char * p) { int idx; <bb 2> [1.00%]: <bb 3> [99.00%]: # idx_10 = PHI <idx_6(3), 0(2)> __builtin_snprintf (p_4(D), 4, "%d", idx_10); idx_6 = idx_10 + 1; if (idx_6 != 1000) goto <bb 3>; [98.99%] else goto <bb 4>; [1.01%] <bb 4> [1.00%]: return; }
I also found the following discussion about what looks like the same problem: https://patchwork.ffmpeg.org/patch/3630
This is because the PHI at that point is only created during CFG changes at the end of VRP1 pass. After creating ASSERT_EXPRs, we still have: <bb 2> [1.00%]: goto <bb 4>; [100.00%] <bb 3> [99.00%]: # RANGE [0, 1000] NONZERO 1023 idx_7 = ASSERT_EXPR <idx_1, idx_1 <= 999>; __builtin_snprintf (p_4(D), 4, "%d", idx_7); # RANGE [1, 1000] NONZERO 1023 idx_6 = idx_7 + 1; <bb 4> [100.00%]: # RANGE [0, 1000] NONZERO 1023 # idx_1 = PHI <0(2), idx_6(3)> if (idx_1 <= 999) goto <bb 3>; [99.00%] else goto <bb 5>; [1.00%] <bb 5> [1.00%]: return; Then VRP1 correctly determines: idx_1: [0, 1000] .MEM_2: VARYING idx_6: [1, 1000] idx_7: [0, 999] EQUIVALENCES: { idx_1 } (1 elements) then the ASSERT_EXPRs are removed, which means whenever idx_7 is used we now use idx_1, and finally the loop is changed, idx_8 and idx_10 is created: <bb 2> [1.00%]: goto <bb 6>; [100.00%] <bb 3> [99.00%]: # RANGE [0, 1000] NONZERO 1023 # idx_10 = PHI <idx_1(4), idx_8(6)> __builtin_snprintf (p_4(D), 4, "%d", idx_10); # RANGE [1, 1000] NONZERO 1023 idx_6 = idx_10 + 1; <bb 4> [99.00%]: # RANGE [0, 1000] NONZERO 1023 # idx_1 = PHI <idx_6(3)> if (idx_1 != 1000) goto <bb 3>; [98.99%] else goto <bb 5>; [1.01%] <bb 5> [1.00%]: return; <bb 6> [1.00%]: # RANGE [0, 1000] NONZERO 1023 # idx_8 = PHI <0(2)> goto <bb 3>; [100.00%] But there is no obvious connection between idx_10 (which indeed nicely could hold the [0, 999] range) and idx_7 (the already removed ASSERT_EXPR); similarly, idx_8 could very well have RANGE [0, 0] but doesn't (though in that case it isn't that big a deal, because it is going to be removed immediately afterwards).
idx_10 addition is a consequence of TODO_update_ssa in vrp1's todo_flags, triggered by jump threading creating the bb6.
*** Bug 80924 has been marked as a duplicate of this bug. ***