[v3,0/7] Fix some libm static issues
[platform/upstream/glibc.git] / libio / tst-vtables-common.c
1 /* Test for libio vtables and their validation.  Common code.
2    Copyright (C) 2018-2024 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with the GNU C Library; if not, see
17    <https://www.gnu.org/licenses/>.  */
18
19 /* This test provides some coverage for how various stdio functions
20    use the vtables in FILE * objects.  The focus is mostly on which
21    functions call which methods, not so much on validating data
22    processing.  An initial series of tests check that custom vtables
23    do not work without activation through _IO_init.
24
25    Note: libio vtables are deprecated feature.  Do not use this test
26    as a documentation source for writing custom vtables.  See
27    fopencookie for a different way of creating custom stdio
28    streams.  */
29
30 #include <stdbool.h>
31 #include <string.h>
32 #include <support/capture_subprocess.h>
33 #include <support/check.h>
34 #include <support/namespace.h>
35 #include <support/support.h>
36 #include <support/test-driver.h>
37 #include <support/xunistd.h>
38
39 #include "libioP.h"
40
41 /* Data shared between the test subprocess and the test driver in the
42    parent.  Note that *shared is reset at the start of the check_call
43    function.  */
44 struct shared
45 {
46   /* Expected file pointer for method calls.  */
47   FILE *fp;
48
49   /* If true, assume that a call to _IO_init is needed to enable
50      custom vtables.  */
51   bool initially_disabled;
52
53   /* Requested return value for the methods which have one.  */
54   int return_value;
55
56   /* A value (usually a character) recorded by some of the methods
57      below.  */
58   int value;
59
60   /* Likewise, for some data.  */
61   char buffer[16];
62   size_t buffer_length;
63
64   /* Total number of method calls.  */
65   unsigned int calls;
66
67   /* Individual method call counts.  */
68   unsigned int calls_finish;
69   unsigned int calls_overflow;
70   unsigned int calls_underflow;
71   unsigned int calls_uflow;
72   unsigned int calls_pbackfail;
73   unsigned int calls_xsputn;
74   unsigned int calls_xsgetn;
75   unsigned int calls_seekoff;
76   unsigned int calls_seekpos;
77   unsigned int calls_setbuf;
78   unsigned int calls_sync;
79   unsigned int calls_doallocate;
80   unsigned int calls_read;
81   unsigned int calls_write;
82   unsigned int calls_seek;
83   unsigned int calls_close;
84   unsigned int calls_stat;
85   unsigned int calls_showmanyc;
86   unsigned int calls_imbue;
87 } *shared;
88
89 /* Method implementations which increment the counters in *shared.  */
90
91 static void
92 log_method (FILE *fp, const char *name)
93 {
94   if (test_verbose > 0)
95     printf ("info: %s (%p) called\n", name, fp);
96 }
97
98 static void
99 method_finish (FILE *fp, int dummy)
100 {
101   log_method (fp, __func__);
102   TEST_VERIFY (fp == shared->fp);
103   ++shared->calls;
104   ++shared->calls_finish;
105 }
106
107 static int
108 method_overflow (FILE *fp, int ch)
109 {
110   log_method (fp, __func__);
111   TEST_VERIFY (fp == shared->fp);
112   ++shared->calls;
113   ++shared->calls_overflow;
114   shared->value = ch;
115   return shared->return_value;
116 }
117
118 static int
119 method_underflow (FILE *fp)
120 {
121   log_method (fp, __func__);
122   TEST_VERIFY (fp == shared->fp);
123   ++shared->calls;
124   ++shared->calls_underflow;
125   return shared->return_value;
126 }
127
128 static int
129 method_uflow (FILE *fp)
130 {
131   log_method (fp, __func__);
132   TEST_VERIFY (fp == shared->fp);
133   ++shared->calls;
134   ++shared->calls_uflow;
135   return shared->return_value;
136 }
137
138 static int
139 method_pbackfail (FILE *fp, int ch)
140 {
141   log_method (fp, __func__);
142   TEST_VERIFY (fp == shared->fp);
143   ++shared->calls;
144   ++shared->calls_pbackfail;
145   shared->value = ch;
146   return shared->return_value;
147 }
148
149 static size_t
150 method_xsputn (FILE *fp, const void *data, size_t n)
151 {
152   log_method (fp, __func__);
153   TEST_VERIFY (fp == shared->fp);
154   ++shared->calls;
155   ++shared->calls_xsputn;
156
157   size_t to_copy = n;
158   if (n > sizeof (shared->buffer))
159     to_copy = sizeof (shared->buffer);
160   memcpy (shared->buffer, data, to_copy);
161   shared->buffer_length = to_copy;
162   return to_copy;
163 }
164
165 static size_t
166 method_xsgetn (FILE *fp, void *data, size_t n)
167 {
168   log_method (fp, __func__);
169   TEST_VERIFY (fp == shared->fp);
170   ++shared->calls;
171   ++shared->calls_xsgetn;
172   return 0;
173 }
174
175 static off64_t
176 method_seekoff (FILE *fp, off64_t offset, int dir, int mode)
177 {
178   log_method (fp, __func__);
179   TEST_VERIFY (fp == shared->fp);
180   ++shared->calls;
181   ++shared->calls_seekoff;
182   return shared->return_value;
183 }
184
185 static off64_t
186 method_seekpos (FILE *fp, off64_t offset, int mode)
187 {
188   log_method (fp, __func__);
189   TEST_VERIFY (fp == shared->fp);
190   ++shared->calls;
191   ++shared->calls_seekpos;
192   return shared->return_value;
193 }
194
195 static FILE *
196 method_setbuf (FILE *fp, char *buffer, ssize_t length)
197 {
198   log_method (fp, __func__);
199   TEST_VERIFY (fp == shared->fp);
200   ++shared->calls;
201   ++shared->calls_setbuf;
202   return fp;
203 }
204
205 static int
206 method_sync (FILE *fp)
207 {
208   log_method (fp, __func__);
209   TEST_VERIFY (fp == shared->fp);
210   ++shared->calls;
211   ++shared->calls_sync;
212   return shared->return_value;
213 }
214
215 static int
216 method_doallocate (FILE *fp)
217 {
218   log_method (fp, __func__);
219   TEST_VERIFY (fp == shared->fp);
220   ++shared->calls;
221   ++shared->calls_doallocate;
222   return shared->return_value;
223 }
224
225 static ssize_t
226 method_read (FILE *fp, void *data, ssize_t length)
227 {
228   log_method (fp, __func__);
229   TEST_VERIFY (fp == shared->fp);
230   ++shared->calls;
231   ++shared->calls_read;
232   return shared->return_value;
233 }
234
235 static ssize_t
236 method_write (FILE *fp, const void *data, ssize_t length)
237 {
238   log_method (fp, __func__);
239   TEST_VERIFY (fp == shared->fp);
240   ++shared->calls;
241   ++shared->calls_write;
242   return shared->return_value;
243 }
244
245 static off64_t
246 method_seek (FILE *fp, off64_t offset, int mode)
247 {
248   log_method (fp, __func__);
249   TEST_VERIFY (fp == shared->fp);
250   ++shared->calls;
251   ++shared->calls_seek;
252   return shared->return_value;
253 }
254
255 static int
256 method_close (FILE *fp)
257 {
258   log_method (fp, __func__);
259   TEST_VERIFY (fp == shared->fp);
260   ++shared->calls;
261   ++shared->calls_close;
262   return shared->return_value;
263 }
264
265 static int
266 method_stat (FILE *fp, void *buffer)
267 {
268   log_method (fp, __func__);
269   TEST_VERIFY (fp == shared->fp);
270   ++shared->calls;
271   ++shared->calls_stat;
272   return shared->return_value;
273 }
274
275 static int
276 method_showmanyc (FILE *fp)
277 {
278   log_method (fp, __func__);
279   TEST_VERIFY (fp == shared->fp);
280   ++shared->calls;
281   ++shared->calls_showmanyc;
282   return shared->return_value;
283 }
284
285 static void
286 method_imbue (FILE *fp, void *locale)
287 {
288   log_method (fp, __func__);
289   TEST_VERIFY (fp == shared->fp);
290   ++shared->calls;
291   ++shared->calls_imbue;
292 }
293
294 /* Our custom vtable.  */
295
296 static const struct _IO_jump_t jumps =
297 {
298   JUMP_INIT_DUMMY,
299   JUMP_INIT (finish, method_finish),
300   JUMP_INIT (overflow, method_overflow),
301   JUMP_INIT (underflow, method_underflow),
302   JUMP_INIT (uflow, method_uflow),
303   JUMP_INIT (pbackfail, method_pbackfail),
304   JUMP_INIT (xsputn, method_xsputn),
305   JUMP_INIT (xsgetn, method_xsgetn),
306   JUMP_INIT (seekoff, method_seekoff),
307   JUMP_INIT (seekpos, method_seekpos),
308   JUMP_INIT (setbuf, method_setbuf),
309   JUMP_INIT (sync, method_sync),
310   JUMP_INIT (doallocate, method_doallocate),
311   JUMP_INIT (read, method_read),
312   JUMP_INIT (write, method_write),
313   JUMP_INIT (seek, method_seek),
314   JUMP_INIT (close, method_close),
315   JUMP_INIT (stat, method_stat),
316   JUMP_INIT (showmanyc, method_showmanyc),
317   JUMP_INIT (imbue, method_imbue)
318 };
319
320 /* Our file implementation.  */
321
322 struct my_file
323 {
324   FILE f;
325   const struct _IO_jump_t *vtable;
326 };
327
328 struct my_file
329 my_file_create (void)
330 {
331   return (struct my_file)
332     {
333       /* Disable locking, so that we do not have to set up a lock
334          pointer.  */
335       .f._flags =  _IO_USER_LOCK,
336
337       /* Copy the offset from the an initialized handle, instead of
338          figuring it out from scratch.  */
339       .f._vtable_offset = stdin->_vtable_offset,
340
341       .vtable = &jumps,
342     };
343 }
344
345 /* Initial tests which do not enable vtable compatibility.  */
346
347 /* Inhibit GCC optimization of fprintf.  */
348 typedef int (*fprintf_type) (FILE *, const char *, ...);
349 static const volatile fprintf_type fprintf_ptr = &fprintf;
350
351 static void
352 without_compatibility_fprintf (void *closure)
353 {
354   /* This call should abort.  */
355   fprintf_ptr (shared->fp, " ");
356   _exit (1);
357 }
358
359 static void
360 without_compatibility_fputc (void *closure)
361 {
362   /* This call should abort.  */
363   fputc (' ', shared->fp);
364   _exit (1);
365 }
366
367 static void
368 without_compatibility_fgetc (void *closure)
369 {
370   /* This call should abort.  */
371   fgetc (shared->fp);
372   _exit (1);
373 }
374
375 static void
376 without_compatibility_fflush (void *closure)
377 {
378   /* This call should abort.  */
379   fflush (shared->fp);
380   _exit (1);
381 }
382
383 static void
384 check_for_termination (const char *name, void (*callback) (void *))
385 {
386   struct my_file file = my_file_create ();
387   shared->fp = &file.f;
388   shared->return_value = -1;
389   shared->calls = 0;
390   struct support_capture_subprocess proc
391     = support_capture_subprocess (callback, NULL);
392   support_capture_subprocess_check (&proc, name, -SIGABRT,
393                                     sc_allow_stderr);
394   const char *message
395     = "Fatal error: glibc detected an invalid stdio handle\n";
396   TEST_COMPARE_BLOB (proc.err.buffer, proc.err.length,
397                      message, strlen (message));
398   TEST_COMPARE (shared->calls, 0);
399   support_capture_subprocess_free (&proc);
400 }
401
402 /* The test with vtable validation disabled.  */
403
404 /* This function does not have a prototype in libioP.h to prevent
405    accidental use from within the library (which would disable vtable
406    verification).  */
407 void _IO_init (FILE *fp, int flags);
408
409 static void
410 with_compatibility_fprintf (void *closure)
411 {
412   /* A temporary staging buffer is used in the current fprintf
413      implementation, which is why there is just one call to
414      xsputn.  */
415   TEST_COMPARE (fprintf_ptr (shared->fp, "A%sCD", "B"), 4);
416   TEST_COMPARE (shared->calls, 1);
417   TEST_COMPARE (shared->calls_xsputn, 1);
418   TEST_COMPARE_BLOB (shared->buffer, shared->buffer_length,
419                      "ABCD", 4);
420 }
421
422 static void
423 with_compatibility_fputc (void *closure)
424 {
425   shared->return_value = '@';
426   TEST_COMPARE (fputc ('@', shared->fp), '@');
427   TEST_COMPARE (shared->calls, 1);
428   TEST_COMPARE (shared->calls_overflow, 1);
429   TEST_COMPARE (shared->value, '@');
430 }
431
432 static void
433 with_compatibility_fgetc (void *closure)
434 {
435   shared->return_value = 'X';
436   TEST_COMPARE (fgetc (shared->fp), 'X');
437   TEST_COMPARE (shared->calls, 1);
438   TEST_COMPARE (shared->calls_uflow, 1);
439 }
440
441 static void
442 with_compatibility_fflush (void *closure)
443 {
444   TEST_COMPARE (fflush (shared->fp), 0);
445   TEST_COMPARE (shared->calls, 1);
446   TEST_COMPARE (shared->calls_sync, 1);
447 }
448
449 /* Call CALLBACK in a subprocess, after setting up a custom file
450    object and updating shared->fp.  */
451 static void
452 check_call (const char *name, void (*callback) (void *),
453             bool initially_disabled)
454 {
455   *shared = (struct shared)
456     {
457      .initially_disabled = initially_disabled,
458     };
459
460   /* Set up a custom file object.  */
461   struct my_file file = my_file_create ();
462   shared->fp = &file.f;
463   if (shared->initially_disabled)
464     _IO_init (shared->fp, file.f._flags);
465
466   if (test_verbose > 0)
467     printf ("info: calling test %s\n", name);
468   support_isolate_in_subprocess (callback, NULL);
469 }
470
471 /* Run the tests.  INITIALLY_DISABLED indicates whether custom vtables
472    are disabled when the test starts.  */
473 static int
474 run_tests (bool initially_disabled)
475 {
476   /* The test relies on fatal error messages being printed to standard
477      error.  */
478   setenv ("LIBC_FATAL_STDERR_", "1", 1);
479
480   shared = support_shared_allocate (sizeof (*shared));
481   shared->initially_disabled = initially_disabled;
482
483   if (initially_disabled)
484     {
485       check_for_termination ("fprintf", without_compatibility_fprintf);
486       check_for_termination ("fputc", without_compatibility_fputc);
487       check_for_termination ("fgetc", without_compatibility_fgetc);
488       check_for_termination ("fflush", without_compatibility_fflush);
489     }
490
491   check_call ("fprintf", with_compatibility_fprintf, initially_disabled);
492   check_call ("fputc", with_compatibility_fputc, initially_disabled);
493   check_call ("fgetc", with_compatibility_fgetc, initially_disabled);
494   check_call ("fflush", with_compatibility_fflush, initially_disabled);
495
496   support_shared_free (shared);
497   shared = NULL;
498
499   return 0;
500 }