diff options
author | Joel Brobecker <brobecker@adacore.com> | 2020-11-15 03:09:44 -0500 |
---|---|---|
committer | Joel Brobecker <brobecker@adacore.com> | 2020-11-15 03:09:44 -0500 |
commit | b34c74ab9a6b8dc0ace3d0cc67bf62de8a74ea00 (patch) | |
tree | cebf5c4bc8bee8820f497d92258416be8e997564 /gdb/unittests | |
parent | 1b4ac058f7daeb9bac9ab0e63a7e73535208dfef (diff) | |
download | gdb-b34c74ab9a6b8dc0ace3d0cc67bf62de8a74ea00.zip gdb-b34c74ab9a6b8dc0ace3d0cc67bf62de8a74ea00.tar.gz gdb-b34c74ab9a6b8dc0ace3d0cc67bf62de8a74ea00.tar.bz2 |
gmp-utils: New API to simply use of GMP's integer/rational/float objects
This API was motivated by a number of reasons:
- GMP's API does not handle "long long" and "unsigned long long",
so using LONGEST and ULONGEST is not straightforward;
- Automate the need to initialize GMP objects before use, and
clear them when no longer used.
However, this API grew also to help with similar matter such
as formatting to a string, and also reading/writing fixed-point
values from byte buffers.
Dedicated unit testing is also added.
gdb/ChangeLog:
* gmp-utils.h, gmp-utils.h: New file.
* unittests/gmp-utils-selftests.c: New file.
* Makefile.in (SUBDIR_UNITTESTS_SRCS): Add
unittests/gmp-utils-selftests.c.
(COMMON_SFILES) Add gmp-utils.c.
(HFILES_NO_SRCDIR): Add gmp-utils.h.
Diffstat (limited to 'gdb/unittests')
-rw-r--r-- | gdb/unittests/gmp-utils-selftests.c | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/gdb/unittests/gmp-utils-selftests.c b/gdb/unittests/gmp-utils-selftests.c new file mode 100644 index 0000000..e8c3c5c --- /dev/null +++ b/gdb/unittests/gmp-utils-selftests.c @@ -0,0 +1,460 @@ +/* Self tests of the gmp-utils API. + + Copyright (C) 2019-2020 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "gmp-utils.h" +#include "gdbsupport/selftest.h" + +#include <math.h> + +namespace selftests { + +/* Perform a series of general tests of gdb_mpz's as_integer method. + + This function tries to be reasonably exhaustive, by testing the edges, + as well as a resonable set of values including negative ones, zero, + and positive values. */ + +static void +gdb_mpz_as_integer () +{ + /* Test a range of values, both as LONGEST and ULONGEST. */ + gdb_mpz v; + LONGEST l_expected; + ULONGEST ul_expected; + + /* Start with the smallest LONGEST */ + l_expected = (LONGEST) 1 << (sizeof (LONGEST) * 8 - 1); + + mpz_ui_pow_ui (v.val, 2, sizeof (LONGEST) * 8 - 1); + mpz_neg (v.val, v.val); + + SELF_CHECK (v.as_integer<LONGEST> () == l_expected); + + /* Try with a small range of integers including negative, zero, + and positive values. */ + for (int i = -256; i <= 256; i++) + { + l_expected = (LONGEST) i; + mpz_set_si (v.val, i); + SELF_CHECK (v.as_integer<LONGEST> () == l_expected); + + if (i >= 0) + { + ul_expected = (ULONGEST) i; + mpz_set_ui (v.val, i); + SELF_CHECK (v.as_integer<ULONGEST> () == ul_expected); + } + } + + /* Try with LONGEST_MAX. */ + l_expected = LONGEST_MAX; + ul_expected = (ULONGEST) l_expected; + + mpz_ui_pow_ui (v.val, 2, sizeof (LONGEST) * 8 - 1); + mpz_sub_ui (v.val, v.val, 1); + + SELF_CHECK (v.as_integer<LONGEST> () == l_expected); + SELF_CHECK (v.as_integer<ULONGEST> () == ul_expected); + + /* Try with ULONGEST_MAX. */ + ul_expected = ULONGEST_MAX; + mpz_ui_pow_ui (v.val, 2, sizeof (LONGEST) * 8); + mpz_sub_ui (v.val, v.val, 1); + + SELF_CHECK (v.as_integer<ULONGEST> () == ul_expected); +} + +/* A helper function to store the given integer value into a buffer, + before reading it back into a gdb_mpz. Sets ACTUAL to the value + read back, while at the same time setting EXPECTED as the value + we would expect to be read back. + + Note that this function does not perform the comparison between + EXPECTED and ACTUAL. The caller will do it inside a SELF_CHECK + call, allowing the line information shown when the test fails + to provide a bit more information about the kind of values + that were used when the check failed. This makes the writing + of the tests a little more verbose, but the debugging in case + of problems should hopefuly be easier. */ + +template<typename T> +void +store_and_read_back (T val, int buf_len, enum bfd_endian byte_order, + gdb_mpz &expected, gdb_mpz &actual) +{ + gdb_byte *buf; + + expected = val; + + buf = (gdb_byte *) alloca (buf_len); + store_integer (buf, buf_len, byte_order, val); + + /* Pre-initialize ACTUAL to something that's not the expected value. */ + mpz_set (actual.val, expected.val); + mpz_sub_ui (actual.val, actual.val, 500); + + actual.read (buf, buf_len, byte_order, !std::is_signed<T>::value); +} + +/* Test the gdb_mpz::read method over a reasonable range of values. + + The testing is done by picking an arbitrary buffer length, after + which we test every possible value that this buffer allows, both + with signed numbers as well as unsigned ones. */ + +static void +gdb_mpz_read_all_from_small () +{ + /* Start with a type whose size is small enough that we can afford + to check the complete range. */ + + int buf_len = 1; + LONGEST l_min = -pow (2, buf_len * 8 - 1); + LONGEST l_max = pow (2, buf_len * 8 - 1) - 1; + + for (LONGEST l = l_min; l <= l_max; l++) + { + gdb_mpz expected, actual; + + store_and_read_back (l, buf_len, BFD_ENDIAN_BIG, expected, actual); + SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0); + + store_and_read_back (l, buf_len, BFD_ENDIAN_LITTLE, expected, actual); + SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0); + } + + /* Do the same as above, but with an unsigned type. */ + ULONGEST ul_min = 0; + ULONGEST ul_max = pow (2, buf_len * 8) - 1; + + for (ULONGEST ul = ul_min; ul <= ul_max; ul++) + { + gdb_mpz expected, actual; + + store_and_read_back (ul, buf_len, BFD_ENDIAN_BIG, expected, actual); + SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0); + + store_and_read_back (ul, buf_len, BFD_ENDIAN_LITTLE, expected, actual); + SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0); + } +} + +/* Test the gdb_mpz::read the extremes of LONGEST and ULONGEST. */ + +static void +gdb_mpz_read_min_max () +{ + gdb_mpz expected, actual; + + /* Start with the smallest LONGEST. */ + + LONGEST l_min = (LONGEST) 1 << (sizeof (LONGEST) * 8 - 1); + + store_and_read_back (l_min, sizeof (LONGEST), BFD_ENDIAN_BIG, + expected, actual); + SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0); + + store_and_read_back (l_min, sizeof (LONGEST), BFD_ENDIAN_LITTLE, + expected, actual); + SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0); + + /* Same with LONGEST_MAX. */ + + LONGEST l_max = LONGEST_MAX; + + store_and_read_back (l_max, sizeof (LONGEST), BFD_ENDIAN_BIG, + expected, actual); + SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0); + + store_and_read_back (l_max, sizeof (LONGEST), BFD_ENDIAN_LITTLE, + expected, actual); + SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0); + + /* Same with the smallest ULONGEST. */ + + ULONGEST ul_min = 0; + + store_and_read_back (ul_min, sizeof (ULONGEST), BFD_ENDIAN_BIG, + expected, actual); + SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0); + + store_and_read_back (ul_min, sizeof (ULONGEST), BFD_ENDIAN_LITTLE, + expected, actual); + SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0); + + /* Same with ULONGEST_MAX. */ + + ULONGEST ul_max = ULONGEST_MAX; + + store_and_read_back (ul_max, sizeof (ULONGEST), BFD_ENDIAN_BIG, + expected, actual); + SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0); + + store_and_read_back (ul_max, sizeof (ULONGEST), BFD_ENDIAN_LITTLE, + expected, actual); + SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0); +} + +/* A helper function which creates a gdb_mpz object from the given + integer VAL, and then writes it using its gdb_mpz::write method. + + The written value is then extracted from the buffer and returned, + for comparison with the original. + + Note that this function does not perform the comparison between + VAL and the returned value. The caller will do it inside a SELF_CHECK + call, allowing the line information shown when the test fails + to provide a bit more information about the kind of values + that were used when the check failed. This makes the writing + of the tests a little more verbose, but the debugging in case + of problems should hopefuly be easier. */ + +template<typename T> +T +write_and_extract (T val, int buf_len, enum bfd_endian byte_order) +{ + gdb_mpz v (val); + + SELF_CHECK (v.as_integer<T> () == val); + + gdb_byte *buf = (gdb_byte *) alloca (buf_len); + v.write (buf, buf_len, byte_order, !std::is_signed<T>::value); + + return extract_integer<T> (buf, buf_len, byte_order); +} + +/* Test the gdb_mpz::write method over a reasonable range of values. + + The testing is done by picking an arbitrary buffer length, after + which we test every possible value that this buffer allows. */ + +static void +gdb_mpz_write_all_from_small () +{ + int buf_len = 1; + LONGEST l_min = -pow (2, buf_len * 8 - 1); + LONGEST l_max = pow (2, buf_len * 8 - 1) - 1; + + for (LONGEST l = l_min; l <= l_max; l++) + { + SELF_CHECK (write_and_extract (l, buf_len, BFD_ENDIAN_BIG) == l); + SELF_CHECK (write_and_extract (l, buf_len, BFD_ENDIAN_LITTLE) == l); + } + + /* Do the same as above, but with an unsigned type. */ + ULONGEST ul_min = 0; + ULONGEST ul_max = pow (2, buf_len * 8) - 1; + + for (ULONGEST ul = ul_min; ul <= ul_max; ul++) + { + SELF_CHECK (write_and_extract (ul, buf_len, BFD_ENDIAN_BIG) == ul); + SELF_CHECK (write_and_extract (ul, buf_len, BFD_ENDIAN_LITTLE) == ul); + } +} + +/* Test the gdb_mpz::write the extremes of LONGEST and ULONGEST. */ + +static void +gdb_mpz_write_min_max () +{ + /* Start with the smallest LONGEST. */ + + LONGEST l_min = (LONGEST) 1 << (sizeof (LONGEST) * 8 - 1); + SELF_CHECK (write_and_extract (l_min, sizeof (LONGEST), BFD_ENDIAN_BIG) + == l_min); + SELF_CHECK (write_and_extract (l_min, sizeof (LONGEST), BFD_ENDIAN_LITTLE) + == l_min); + + /* Same with LONGEST_MAX. */ + + LONGEST l_max = LONGEST_MAX; + SELF_CHECK (write_and_extract (l_max, sizeof (LONGEST), BFD_ENDIAN_BIG) + == l_max); + SELF_CHECK (write_and_extract (l_max, sizeof (LONGEST), BFD_ENDIAN_LITTLE) + == l_max); + + /* Same with the smallest ULONGEST. */ + + ULONGEST ul_min = (ULONGEST) 1 << (sizeof (ULONGEST) * 8 - 1); + SELF_CHECK (write_and_extract (ul_min, sizeof (ULONGEST), BFD_ENDIAN_BIG) + == ul_min); + SELF_CHECK (write_and_extract (ul_min, sizeof (ULONGEST), BFD_ENDIAN_LITTLE) + == ul_min); + + /* Same with ULONGEST_MAX. */ + + ULONGEST ul_max = ULONGEST_MAX; + SELF_CHECK (write_and_extract (ul_max, sizeof (ULONGEST), BFD_ENDIAN_BIG) + == ul_max); + SELF_CHECK (write_and_extract (ul_max, sizeof (ULONGEST), BFD_ENDIAN_LITTLE) + == ul_max); +} + +/* A helper function which stores the signed number, the unscaled value + of a fixed point object, into a buffer, and then uses gdb_mpq's + read_fixed_point to read it as a fixed_point value, with + the given parameters. + + EXPECTED is set to the value we expected to get after the call + to read_fixed_point. ACTUAL is the value we actually do get. + + Note that this function does not perform the comparison between + EXPECTED and ACTUAL. The caller will do it inside a SELF_CHECK + call, allowing the line information shown when the test fails + to provide a bit more information about the kind of values + that were used when the check failed. This makes the writing + of the tests a little more verbose, but the debugging in case + of problems should hopefuly be easier. */ + +static void +read_fp_test (int unscaled, const gdb_mpq &scaling_factor, + enum bfd_endian byte_order, + gdb_mpq &expected, gdb_mpq &actual) +{ + /* For this kind of testing, we'll use a buffer the same size as + our unscaled parameter. */ + const int len = sizeof (unscaled); + gdb_byte buf[len]; + store_signed_integer (buf, len, byte_order, unscaled); + + actual.read_fixed_point (buf, len, byte_order, 0, scaling_factor); + + mpq_set_si (expected.val, unscaled, 1); + mpq_mul (expected.val, expected.val, scaling_factor.val); +} + +/* Perform various tests of the gdb_mpq::read_fixed_point method. */ + +static void +gdb_mpq_read_fixed_point () +{ + gdb_mpq expected, actual; + gdb_mpq scaling_factor; + + /* Pick an arbitrary scaling_factor; this operation is trivial enough + thanks to GMP that the value we use isn't really important. */ + mpq_set_ui (scaling_factor.val, 3, 5); + + /* Try a few values, both negative and positive... */ + + read_fp_test (-256, scaling_factor, BFD_ENDIAN_BIG, expected, actual); + SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0); + read_fp_test (-256, scaling_factor, BFD_ENDIAN_LITTLE, expected, actual); + SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0); + + read_fp_test (-1, scaling_factor, BFD_ENDIAN_BIG, expected, actual); + SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0); + read_fp_test (-1, scaling_factor, BFD_ENDIAN_LITTLE, expected, actual); + SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0); + + read_fp_test (0, scaling_factor, BFD_ENDIAN_BIG, expected, actual); + SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0); + read_fp_test (0, scaling_factor, BFD_ENDIAN_LITTLE, expected, actual); + SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0); + + read_fp_test (1, scaling_factor, BFD_ENDIAN_BIG, expected, actual); + SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0); + read_fp_test (1, scaling_factor, BFD_ENDIAN_LITTLE, expected, actual); + SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0); + + read_fp_test (1025, scaling_factor, BFD_ENDIAN_BIG, expected, actual); + SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0); + read_fp_test (1025, scaling_factor, BFD_ENDIAN_LITTLE, expected, actual); + SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0); +} + +/* A helper function which builds a gdb_mpq object from the given + NUMERATOR and DENOMINATOR, and then calls gdb_mpq's write_fixed_point + method to write it to a buffer. + + The value written into the buffer is then read back as is, + and returned. */ + +static LONGEST +write_fp_test (int numerator, unsigned int denominator, + const gdb_mpq &scaling_factor, + enum bfd_endian byte_order) +{ + /* For this testing, we'll use a buffer the size of LONGEST. + This is really an arbitrary decision, as long as the buffer + is long enough to hold the unscaled values that we'll be + writing. */ + const int len = sizeof (LONGEST); + gdb_byte buf[len]; + memset (buf, 0, len); + + gdb_mpq v; + mpq_set_ui (v.val, numerator, denominator); + mpq_canonicalize (v.val); + v.write_fixed_point (buf, len, byte_order, 0, scaling_factor); + + return extract_unsigned_integer (buf, len, byte_order); +} + +/* Perform various tests of the gdb_mpq::write_fixed_point method. */ + +static void +gdb_mpq_write_fixed_point () +{ + /* Pick an arbitrary factor; this operations is sufficiently trivial + with the use of GMP that the value of this factor is not really + all that important. */ + gdb_mpq scaling_factor; + mpq_set_ui (scaling_factor.val, 1, 3); + + gdb_mpq vq; + + /* Try a few multiples of the scaling factor, both negative, + and positive... */ + + SELF_CHECK (write_fp_test (-8, 1, scaling_factor, BFD_ENDIAN_BIG) == -24); + SELF_CHECK (write_fp_test (-8, 1, scaling_factor, BFD_ENDIAN_LITTLE) == -24); + + SELF_CHECK (write_fp_test (-2, 3, scaling_factor, BFD_ENDIAN_BIG) == -2); + SELF_CHECK (write_fp_test (-2, 3, scaling_factor, BFD_ENDIAN_LITTLE) == -2); + + SELF_CHECK (write_fp_test (0, 3, scaling_factor, BFD_ENDIAN_BIG) == 0); + SELF_CHECK (write_fp_test (0, 3, scaling_factor, BFD_ENDIAN_LITTLE) == 0); + + SELF_CHECK (write_fp_test (5, 3, scaling_factor, BFD_ENDIAN_BIG) == 5); + SELF_CHECK (write_fp_test (5, 3, scaling_factor, BFD_ENDIAN_LITTLE) == 5); +} + +} + +void _initialize_gmp_utils_selftests (); + +void +_initialize_gmp_utils_selftests () +{ + selftests::register_test ("gdb_mpz_as_integer", + selftests::gdb_mpz_as_integer); + selftests::register_test ("gdb_mpz_read_all_from_small", + selftests::gdb_mpz_read_all_from_small); + selftests::register_test ("gdb_mpz_read_min_max", + selftests::gdb_mpz_read_min_max); + selftests::register_test ("gdb_mpz_write_all_from_small", + selftests::gdb_mpz_write_all_from_small); + selftests::register_test ("gdb_mpz_write_min_max", + selftests::gdb_mpz_write_min_max); + selftests::register_test ("gdb_mpq_read_fixed_point", + selftests::gdb_mpq_read_fixed_point); + selftests::register_test ("gdb_mpq_write_fixed_point", + selftests::gdb_mpq_write_fixed_point); +} |