From patchwork Tue Jun 26 10:37:24 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 28048 Received: (qmail 48568 invoked by alias); 26 Jun 2018 10:37:31 -0000 Mailing-List: contact libc-alpha-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: libc-alpha-owner@sourceware.org Delivered-To: mailing list libc-alpha@sourceware.org Received: (qmail 48551 invoked by uid 89); 26 Jun 2018 10:37:31 -0000 Authentication-Results: sourceware.org; auth=none X-Spam-SWARE-Status: No, score=-24.7 required=5.0 tests=AWL, BAYES_00, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3, KAM_SHORT, SPF_HELO_PASS autolearn=ham version=3.3.2 spammy=Initial X-HELO: mx1.redhat.com Subject: Re: [PATCH] libio: Add new test tst-vtables To: libc-alpha@sourceware.org References: From: Florian Weimer Message-ID: <29920a3a-c5be-2f43-76b4-4f014791d4b5@redhat.com> Date: Tue, 26 Jun 2018 12:37:24 +0200 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Thunderbird/52.8.0 MIME-Version: 1.0 In-Reply-To: Here is what I committed. Florian Subject: [PATCH COMMITTED] libio: Add tst-vtables, tst-vtables-interposed To: libc-alpha@sourceware.org 2018-06-26 Florian Weimer * libio/Makefile (tests-internal): Add tst-vtables, tst-vtables-interposed. * libio/tst-vtables.c: New file. * libio/tst-vtables-common.c: Likewise. * libio/tst-vtables-interposed.c: Likewise. diff --git a/libio/Makefile b/libio/Makefile index cbe14a8e25..64d283e512 100644 --- a/libio/Makefile +++ b/libio/Makefile @@ -66,6 +66,8 @@ tests = tst_swprintf tst_wprintf tst_swscanf tst_wscanf tst_getwc tst_putwc \ tst-fwrite-error tst-ftell-partial-wide tst-ftell-active-handler \ tst-ftell-append tst-fputws tst-bz22415 tst-fgetc-after-eof +tests-internal = tst-vtables tst-vtables-interposed + ifeq (yes,$(build-shared)) # Add test-fopenloc only if shared library is enabled since it depends on # shared localedata objects. diff --git a/libio/tst-vtables-common.c b/libio/tst-vtables-common.c new file mode 100644 index 0000000000..5e31012069 --- /dev/null +++ b/libio/tst-vtables-common.c @@ -0,0 +1,513 @@ +/* Test for libio vtables and their validation. Common code. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +/* This test provides some coverage for how various stdio functions + use the vtables in FILE * objects. The focus is mostly on which + functions call which methods, not so much on validating data + processing. An initial series of tests check that custom vtables + do not work without activation through _IO_init. + + Note: libio vtables are deprecated feature. Do not use this test + as a documentation source for writing custom vtables. See + fopencookie for a different way of creating custom stdio + streams. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libioP.h" + +/* Data shared between the test subprocess and the test driver in the + parent. Note that *shared is reset at the start of the check_call + function. */ +struct shared +{ + /* Expected file pointer for method calls. */ + FILE *fp; + + /* If true, assume that a call to _IO_init is needed to enable + custom vtables. */ + bool initially_disabled; + + /* Requested return value for the methods which have one. */ + int return_value; + + /* A value (usually a character) recorded by some of the methods + below. */ + int value; + + /* Likewise, for some data. */ + char buffer[16]; + size_t buffer_length; + + /* Total number of method calls. */ + unsigned int calls; + + /* Individual method call counts. */ + unsigned int calls_finish; + unsigned int calls_overflow; + unsigned int calls_underflow; + unsigned int calls_uflow; + unsigned int calls_pbackfail; + unsigned int calls_xsputn; + unsigned int calls_xsgetn; + unsigned int calls_seekoff; + unsigned int calls_seekpos; + unsigned int calls_setbuf; + unsigned int calls_sync; + unsigned int calls_doallocate; + unsigned int calls_read; + unsigned int calls_write; + unsigned int calls_seek; + unsigned int calls_close; + unsigned int calls_stat; + unsigned int calls_showmanyc; + unsigned int calls_imbue; +} *shared; + +/* Method implementations which increment the counters in *shared. */ + +static void +log_method (FILE *fp, const char *name) +{ + if (test_verbose > 0) + printf ("info: %s (%p) called\n", name, fp); +} + +static void +method_finish (FILE *fp, int dummy) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_finish; +} + +static int +method_overflow (FILE *fp, int ch) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_overflow; + shared->value = ch; + return shared->return_value; +} + +static int +method_underflow (FILE *fp) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_underflow; + return shared->return_value; +} + +static int +method_uflow (FILE *fp) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_uflow; + return shared->return_value; +} + +static int +method_pbackfail (FILE *fp, int ch) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_pbackfail; + shared->value = ch; + return shared->return_value; +} + +static size_t +method_xsputn (FILE *fp, const void *data, size_t n) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_xsputn; + + size_t to_copy = n; + if (n > sizeof (shared->buffer)) + to_copy = sizeof (shared->buffer); + memcpy (shared->buffer, data, to_copy); + shared->buffer_length = to_copy; + return to_copy; +} + +static size_t +method_xsgetn (FILE *fp, void *data, size_t n) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_xsgetn; + return 0; +} + +static off64_t +method_seekoff (FILE *fp, off64_t offset, int dir, int mode) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_seekoff; + return shared->return_value; +} + +static off64_t +method_seekpos (FILE *fp, off64_t offset, int mode) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_seekpos; + return shared->return_value; +} + +static FILE * +method_setbuf (FILE *fp, char *buffer, ssize_t length) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_setbuf; + return fp; +} + +static int +method_sync (FILE *fp) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_sync; + return shared->return_value; +} + +static int +method_doallocate (FILE *fp) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_doallocate; + return shared->return_value; +} + +static ssize_t +method_read (FILE *fp, void *data, ssize_t length) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_read; + return shared->return_value; +} + +static ssize_t +method_write (FILE *fp, const void *data, ssize_t length) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_write; + return shared->return_value; +} + +static off64_t +method_seek (FILE *fp, off64_t offset, int mode) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_seek; + return shared->return_value; +} + +static int +method_close (FILE *fp) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_close; + return shared->return_value; +} + +static int +method_stat (FILE *fp, void *buffer) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_stat; + return shared->return_value; +} + +static int +method_showmanyc (FILE *fp) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_showmanyc; + return shared->return_value; +} + +static void +method_imbue (FILE *fp, void *locale) +{ + log_method (fp, __func__); + TEST_VERIFY (fp == shared->fp); + ++shared->calls; + ++shared->calls_imbue; +} + +/* Our custom vtable. */ + +static const struct _IO_jump_t jumps = +{ + JUMP_INIT_DUMMY, + JUMP_INIT (finish, method_finish), + JUMP_INIT (overflow, method_overflow), + JUMP_INIT (underflow, method_underflow), + JUMP_INIT (uflow, method_uflow), + JUMP_INIT (pbackfail, method_pbackfail), + JUMP_INIT (xsputn, method_xsputn), + JUMP_INIT (xsgetn, method_xsgetn), + JUMP_INIT (seekoff, method_seekoff), + JUMP_INIT (seekpos, method_seekpos), + JUMP_INIT (setbuf, method_setbuf), + JUMP_INIT (sync, method_sync), + JUMP_INIT (doallocate, method_doallocate), + JUMP_INIT (read, method_read), + JUMP_INIT (write, method_write), + JUMP_INIT (seek, method_seek), + JUMP_INIT (close, method_close), + JUMP_INIT (stat, method_stat), + JUMP_INIT (showmanyc, method_showmanyc), + JUMP_INIT (imbue, method_imbue) +}; + +/* Our file implementation. */ + +struct my_file +{ + FILE f; + const struct _IO_jump_t *vtable; +}; + +struct my_file +my_file_create (void) +{ + return (struct my_file) + { + /* Disable locking, so that we do not have to set up a lock + pointer. */ + .f._flags = _IO_USER_LOCK, + + /* Copy the offset from the an initialized handle, instead of + figuring it out from scratch. */ + .f._vtable_offset = stdin->_vtable_offset, + + .vtable = &jumps, + }; +} + +/* Initial tests which do not enable vtable compatibility. */ + +/* Inhibit GCC optimization of fprintf. */ +typedef int (*fprintf_type) (FILE *, const char *, ...); +static const volatile fprintf_type fprintf_ptr = &fprintf; + +static void +without_compatibility_fprintf (void *closure) +{ + /* This call should abort. */ + fprintf_ptr (shared->fp, " "); + _exit (1); +} + +static void +without_compatibility_fputc (void *closure) +{ + /* This call should abort. */ + fputc (' ', shared->fp); + _exit (1); +} + +static void +without_compatibility_fgetc (void *closure) +{ + /* This call should abort. */ + fgetc (shared->fp); + _exit (1); +} + +static void +without_compatibility_fflush (void *closure) +{ + /* This call should abort. */ + fflush (shared->fp); + _exit (1); +} + +/* Exit status after abnormal termination. */ +static int termination_status; + +static void +init_termination_status (void) +{ + pid_t pid = xfork (); + if (pid == 0) + abort (); + xwaitpid (pid, &termination_status, 0); + + TEST_VERIFY (WIFSIGNALED (termination_status)); + TEST_COMPARE (WTERMSIG (termination_status), SIGABRT); +} + +static void +check_for_termination (const char *name, void (*callback) (void *)) +{ + struct my_file file = my_file_create (); + shared->fp = &file.f; + shared->return_value = -1; + shared->calls = 0; + struct support_capture_subprocess proc + = support_capture_subprocess (callback, NULL); + support_capture_subprocess_check (&proc, name, termination_status, + sc_allow_stderr); + const char *message + = "Fatal error: glibc detected an invalid stdio handle\n"; + TEST_COMPARE_BLOB (proc.err.buffer, proc.err.length, + message, strlen (message)); + TEST_COMPARE (shared->calls, 0); + support_capture_subprocess_free (&proc); +} + +/* The test with vtable validation disabled. */ + +/* This function does not have a prototype in libioP.h to prevent + accidental use from within the library (which would disable vtable + verification). */ +void _IO_init (FILE *fp, int flags); + +static void +with_compatibility_fprintf (void *closure) +{ + TEST_COMPARE (fprintf_ptr (shared->fp, "A%sCD", "B"), 4); + TEST_COMPARE (shared->calls, 3); + TEST_COMPARE (shared->calls_xsputn, 3); + TEST_COMPARE_BLOB (shared->buffer, shared->buffer_length, + "CD", 2); +} + +static void +with_compatibility_fputc (void *closure) +{ + shared->return_value = '@'; + TEST_COMPARE (fputc ('@', shared->fp), '@'); + TEST_COMPARE (shared->calls, 1); + TEST_COMPARE (shared->calls_overflow, 1); + TEST_COMPARE (shared->value, '@'); +} + +static void +with_compatibility_fgetc (void *closure) +{ + shared->return_value = 'X'; + TEST_COMPARE (fgetc (shared->fp), 'X'); + TEST_COMPARE (shared->calls, 1); + TEST_COMPARE (shared->calls_uflow, 1); +} + +static void +with_compatibility_fflush (void *closure) +{ + TEST_COMPARE (fflush (shared->fp), 0); + TEST_COMPARE (shared->calls, 1); + TEST_COMPARE (shared->calls_sync, 1); +} + +/* Call CALLBACK in a subprocess, after setting up a custom file + object and updating shared->fp. */ +static void +check_call (const char *name, void (*callback) (void *), + bool initially_disabled) +{ + *shared = (struct shared) + { + .initially_disabled = initially_disabled, + }; + + /* Set up a custom file object. */ + struct my_file file = my_file_create (); + shared->fp = &file.f; + if (shared->initially_disabled) + _IO_init (shared->fp, file.f._flags); + + if (test_verbose > 0) + printf ("info: calling test %s\n", name); + support_isolate_in_subprocess (callback, NULL); +} + +/* Run the tests. INITIALLY_DISABLED indicates whether custom vtables + are disabled when the test starts. */ +static int +run_tests (bool initially_disabled) +{ + /* The test relies on fatal error messages being printed to standard + error. */ + setenv ("LIBC_FATAL_STDERR_", "1", 1); + + shared = support_shared_allocate (sizeof (*shared)); + shared->initially_disabled = initially_disabled; + init_termination_status (); + + if (initially_disabled) + { + check_for_termination ("fprintf", without_compatibility_fprintf); + check_for_termination ("fputc", without_compatibility_fputc); + check_for_termination ("fgetc", without_compatibility_fgetc); + check_for_termination ("fflush", without_compatibility_fflush); + } + + check_call ("fprintf", with_compatibility_fprintf, initially_disabled); + check_call ("fputc", with_compatibility_fputc, initially_disabled); + check_call ("fgetc", with_compatibility_fgetc, initially_disabled); + check_call ("fflush", with_compatibility_fflush, initially_disabled); + + support_shared_free (shared); + shared = NULL; + + return 0; +} diff --git a/libio/tst-vtables-interposed.c b/libio/tst-vtables-interposed.c new file mode 100644 index 0000000000..aaa5a84e0a --- /dev/null +++ b/libio/tst-vtables-interposed.c @@ -0,0 +1,36 @@ +/* Test for libio vtables and their validation. Enabled through interposition. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include "tst-vtables-common.c" + +/* Provide an interposed definition of the standard file handles with + our own vtable. stdout/stdin/stderr will not work as a result, but + a succesful test does not print anything, so this is fine. */ +#define _IO_file_jumps jumps +#include "stdfiles.c" + +static int +do_test (void) +{ + return run_tests (false); +} + +/* Calling setvbuf in the test driver is not supported with our + interposed file handles. */ +#define TEST_NO_SETVBUF +#include diff --git a/libio/tst-vtables.c b/libio/tst-vtables.c new file mode 100644 index 0000000000..c31a552f61 --- /dev/null +++ b/libio/tst-vtables.c @@ -0,0 +1,27 @@ +/* Test for libio vtables and their validation. Initially disabled case. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include "tst-vtables-common.c" + +static int +do_test (void) +{ + return run_tests (true); +} + +#include