aboutsummaryrefslogtreecommitdiff
path: root/gdb/unittests
diff options
context:
space:
mode:
authorJoel Brobecker <brobecker@adacore.com>2020-11-15 03:09:44 -0500
committerJoel Brobecker <brobecker@adacore.com>2020-11-15 03:09:44 -0500
commitb34c74ab9a6b8dc0ace3d0cc67bf62de8a74ea00 (patch)
treecebf5c4bc8bee8820f497d92258416be8e997564 /gdb/unittests
parent1b4ac058f7daeb9bac9ab0e63a7e73535208dfef (diff)
downloadgdb-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.c460
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);
+}