2222// static.
2323#include "message.c"
2424
25+ #ifndef MIN
26+ # define MIN (x ,y ) ((x) < (y) ? (x) : (y))
27+ #endif
28+
29+ // These formats are not standard in C printf() function.
30+ // Use a global variable rather than a literal format to disable
31+ // -Wformat compiler warnings:
32+ //
33+ // - warning: '0' flag used with ‘%p’ gnu_printf format
34+ // - warning: format ‘%S’ expects argument of type ‘wchar_t *’, but argument 4 has type ‘char *’
35+ // - warning: unknown conversion type character ‘b’ in format
36+ //
37+ // These formats are in practise only used from vim script printf()
38+ // function and never as literals in C code.
39+ char * fmt_012p = "%012p" ;
40+ char * fmt_5S = "%5S" ;
41+ char * fmt_06b = "%06b" ;
42+
2543/*
2644 * Test trunc_string().
2745 */
@@ -93,6 +111,149 @@ test_trunc_string(void)
93111 vim_free (s );
94112}
95113
114+ /*
115+ * Test vim_snprintf() with a focus on checking that truncation is
116+ * correct when buffer is small, since it cannot be tested from
117+ * vim scrip tests. Check that:
118+ * - no buffer overflows happens (with valgrind or asan)
119+ * - output string is always NUL terminated.
120+ *
121+ * Not all formats of vim_snprintf() are checked here. They are
122+ * checked more exhaustively in Test_printf*() vim script tests.
123+ */
124+ static void
125+ test_vim_snprintf (void )
126+ {
127+ int n ;
128+ size_t bsize ;
129+ int bsize_int ;
130+ char * ptr = (char * )0x87654321 ;
131+
132+ // Loop on various buffer sizes to make sure that truncation of
133+ // vim_snprintf() is correct.
134+ for (bsize = 0 ; bsize < 15 ; ++ bsize )
135+ {
136+ bsize_int = (int )bsize - 1 ;
137+
138+ // buf is the heap rather than in the stack
139+ // so valgrind can detect buffer overflows if any.
140+ // Use malloc() rather than alloc() as test checks with 0-size
141+ // buffer and its content should then never be used.
142+ char * buf = malloc (bsize );
143+
144+ n = vim_snprintf (buf , bsize , "%d" , 1234567 );
145+ assert (n == 7 );
146+ assert (bsize == 0 || STRNCMP (buf , "1234567" , bsize_int ) == 0 );
147+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
148+
149+ n = vim_snprintf (buf , bsize , "%ld" , 1234567L );
150+ assert (n == 7 );
151+ assert (bsize == 0 || STRNCMP (buf , "1234567" , bsize_int ) == 0 );
152+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
153+
154+ n = vim_snprintf (buf , bsize , "%9ld" , 1234567L );
155+ assert (n == 9 );
156+ assert (bsize == 0 || STRNCMP (buf , " 1234567" , bsize_int ) == 0 );
157+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
158+
159+ n = vim_snprintf (buf , bsize , "%-9ld" , 1234567L );
160+ assert (n == 9 );
161+ assert (bsize == 0 || STRNCMP (buf , "1234567 " , bsize_int ) == 0 );
162+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
163+
164+ n = vim_snprintf (buf , bsize , "%x" , 0xdeadbeef );
165+ assert (n == 8 );
166+ assert (bsize == 0 || STRNCMP (buf , "deadbeef" , bsize_int ) == 0 );
167+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
168+
169+ n = vim_snprintf (buf , bsize , fmt_06b , 12 );
170+ assert (n == 6 );
171+ assert (bsize == 0 || STRNCMP (buf , "001100" , bsize_int ) == 0 );
172+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
173+
174+ #ifdef FEAT_FLOAT
175+ n = vim_snprintf (buf , bsize , "%f" , 1.234 );
176+ assert (n == 8 );
177+ assert (bsize == 0 || STRNCMP (buf , "1.234000" , bsize_int ) == 0 );
178+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
179+
180+ n = vim_snprintf (buf , bsize , "%e" , 1.234 );
181+ assert (n == 12 );
182+ assert (bsize == 0 || STRNCMP (buf , "1.234000e+00" , bsize_int ) == 0 );
183+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
184+
185+ n = vim_snprintf (buf , bsize , "%f" , 0.0 /0.0 );
186+ assert (n == 3 );
187+ assert (bsize == 0 || STRNCMP (buf , "nan" , bsize_int ) == 0 );
188+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
189+
190+ n = vim_snprintf (buf , bsize , "%f" , 1.0 /0.0 );
191+ assert (n == 3 );
192+ assert (bsize == 0 || STRNCMP (buf , "inf" , bsize_int ) == 0 );
193+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
194+
195+ n = vim_snprintf (buf , bsize , "%f" , -1.0 /0.0 );
196+ assert (n == 4 );
197+ assert (bsize == 0 || STRNCMP (buf , "-inf" , bsize_int ) == 0 );
198+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
199+
200+ n = vim_snprintf (buf , bsize , "%f" , -0.0 );
201+ assert (n == 9 );
202+ assert (bsize == 0 || STRNCMP (buf , "-0.000000" , bsize_int ) == 0 );
203+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
204+ #endif
205+
206+ n = vim_snprintf (buf , bsize , "%s" , "漢語" );
207+ assert (n == 6 );
208+ assert (bsize == 0 || STRNCMP (buf , "漢語" , bsize_int ) == 0 );
209+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
210+
211+ n = vim_snprintf (buf , bsize , "%8s" , "漢語" );
212+ assert (n == 8 );
213+ assert (bsize == 0 || STRNCMP (buf , " 漢語" , bsize_int ) == 0 );
214+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
215+
216+ n = vim_snprintf (buf , bsize , "%-8s" , "漢語" );
217+ assert (n == 8 );
218+ assert (bsize == 0 || STRNCMP (buf , "漢語 " , bsize_int ) == 0 );
219+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
220+
221+ n = vim_snprintf (buf , bsize , "%.3s" , "漢語" );
222+ assert (n == 3 );
223+ assert (bsize == 0 || STRNCMP (buf , "漢" , bsize_int ) == 0 );
224+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
225+
226+ n = vim_snprintf (buf , bsize , fmt_5S , "foo" );
227+ assert (n == 5 );
228+ assert (bsize == 0 || STRNCMP (buf , " foo" , bsize_int ) == 0 );
229+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
230+
231+ n = vim_snprintf (buf , bsize , "%%%%%%" );
232+ assert (n == 3 );
233+ assert (bsize == 0 || STRNCMP (buf , "%%%" , bsize_int ) == 0 );
234+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
235+
236+ n = vim_snprintf (buf , bsize , "%c%c" , 1 , 2 );
237+ assert (n == 2 );
238+ assert (bsize == 0 || STRNCMP (buf , "\x01\x02" , bsize_int ) == 0 );
239+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
240+
241+ // %p format is not tested in vim script tests Test_printf*()
242+ // as it only makes sense in C code.
243+ n = vim_snprintf (buf , bsize , "%p" , ptr );
244+ assert (n == 10 );
245+ assert (bsize == 0 || STRNCMP (buf , "0x87654321" , bsize_int ) == 0 );
246+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
247+
248+ n = vim_snprintf (buf , bsize , fmt_012p , ptr );
249+ assert (n == 12 );
250+ assert (bsize == 0 || STRNCMP (buf , "0x0087654321" , bsize_int ) == 0 );
251+ assert (bsize == 0 || buf [MIN (n , bsize_int )] == '\0' );
252+
253+ free (buf );
254+ }
255+ }
256+
96257 int
97258main (int argc , char * * argv )
98259{
@@ -104,10 +265,12 @@ main(int argc, char **argv)
104265 set_option_value ((char_u * )"encoding" , 0 , (char_u * )"utf-8" , 0 );
105266 init_chartab ();
106267 test_trunc_string ();
268+ test_vim_snprintf ();
107269
108270 set_option_value ((char_u * )"encoding" , 0 , (char_u * )"latin1" , 0 );
109271 init_chartab ();
110272 test_trunc_string ();
273+ test_vim_snprintf ();
111274
112275 return 0 ;
113276}
0 commit comments