From b34c74ab9a6b8dc0ace3d0cc67bf62de8a74ea00 Mon Sep 17 00:00:00 2001 From: Joel Brobecker Date: Sun, 15 Nov 2020 03:09:44 -0500 Subject: [PATCH] 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. --- gdb/ChangeLog | 9 + gdb/Makefile.in | 3 + gdb/gmp-utils.c | 172 +++++++++++ gdb/gmp-utils.h | 282 +++++++++++++++++ gdb/unittests/gmp-utils-selftests.c | 460 ++++++++++++++++++++++++++++ 5 files changed, 926 insertions(+) create mode 100644 gdb/gmp-utils.c create mode 100644 gdb/gmp-utils.h create mode 100644 gdb/unittests/gmp-utils-selftests.c diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 8de390a7c72..a0291092e29 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,12 @@ +2020-11-15 Joel Brobecker + + * 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. + 2020-11-15 Joel Brobecker * configure.ac: Generate an error if a usable GMP library diff --git a/gdb/Makefile.in b/gdb/Makefile.in index c461964837f..9b48f737530 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -446,6 +446,7 @@ SELFTESTS_SRCS = \ unittests/filtered_iterator-selftests.c \ unittests/format_pieces-selftests.c \ unittests/function-view-selftests.c \ + unittests/gmp-utils-selftests.c \ unittests/lookup_name_info-selftests.c \ unittests/memory-map-selftests.c \ unittests/memrange-selftests.c \ @@ -1059,6 +1060,7 @@ COMMON_SFILES = \ gdb_regex.c \ gdbarch.c \ gdbtypes.c \ + gmp-utils.c \ gnu-v2-abi.c \ gnu-v3-abi.c \ go-lang.c \ @@ -1304,6 +1306,7 @@ HFILES_NO_SRCDIR = \ gdbthread.h \ gdbtypes.h \ glibc-tdep.h \ + gmp-utils.h \ gnu-nat.h \ go-lang.h \ gregset.h \ diff --git a/gdb/gmp-utils.c b/gdb/gmp-utils.c new file mode 100644 index 00000000000..db92e57316c --- /dev/null +++ b/gdb/gmp-utils.c @@ -0,0 +1,172 @@ +/* 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 . */ + +#include "gmp-utils.h" + +/* See gmp-utils.h. */ + +gdb::unique_xmalloc_ptr +gmp_string_asprintf (const char *fmt, ...) +{ + va_list vp; + char *buf; + + va_start (vp, fmt); + gmp_vasprintf (&buf, fmt, vp); + va_end (vp); + + return gdb::unique_xmalloc_ptr (buf); +} + +/* See gmp-utils.h. */ + +void +gdb_mpz::read (const gdb_byte *buf, int len, enum bfd_endian byte_order, + bool unsigned_p) +{ + mpz_import (val, 1 /* count */, -1 /* order */, len /* size */, + byte_order == BFD_ENDIAN_BIG ? 1 : -1 /* endian */, + 0 /* nails */, buf /* op */); + + if (!unsigned_p) + { + /* The value was imported as if it was a positive value, + as mpz_import does not handle signs. If the original value + was in fact negative, we need to adjust VAL accordingly. */ + gdb_mpz max; + + mpz_ui_pow_ui (max.val, 2, len * TARGET_CHAR_BIT - 1); + if (mpz_cmp (val, max.val) >= 0) + mpz_submul_ui (val, max.val, 2); + } +} + +/* See gmp-utils.h. */ + +void +gdb_mpz::write (gdb_byte *buf, int len, enum bfd_endian byte_order, + bool unsigned_p) const +{ + gdb_mpz exported_val (val); + + if (mpz_cmp_ui (val, 0) < 0) + { + /* mpz_export does not handle signed values, so create a positive + value whose bit representation as an unsigned of the same length + would be the same as our negative value. */ + gdb_mpz neg_offset; + + mpz_ui_pow_ui (neg_offset.val, 2, len * TARGET_CHAR_BIT); + mpz_add (exported_val.val, exported_val.val, neg_offset.val); + } + + /* Start by clearing the buffer, as mpz_export only writes as many + bytes as it needs (including none, if the value to export is zero. */ + memset (buf, 0, len); + mpz_export (buf, NULL /* count */, -1 /* order */, len /* size */, + byte_order == BFD_ENDIAN_BIG ? 1 : -1 /* endian */, + 0 /* nails */, exported_val.val); +} + +/* See gmp-utils.h. */ + +gdb_mpz +gdb_mpq::get_rounded () const +{ + /* Work with a positive number so as to make the "floor" rounding + always round towards zero. */ + + gdb_mpq abs_val (val); + mpq_abs (abs_val.val, abs_val.val); + + /* Convert our rational number into a quotient and remainder, + with "floor" rounding, which in our case means rounding + towards zero. */ + + gdb_mpz quotient, remainder; + mpz_fdiv_qr (quotient.val, remainder.val, + mpq_numref (abs_val.val), mpq_denref (abs_val.val)); + + /* Multiply the remainder by 2, and see if it is greater or equal + to abs_val's denominator. If yes, round to the next integer. */ + + mpz_mul_ui (remainder.val, remainder.val, 2); + if (mpz_cmp (remainder.val, mpq_denref (abs_val.val)) >= 0) + mpz_add_ui (quotient.val, quotient.val, 1); + + /* Re-apply the sign if needed. */ + if (mpq_sgn (val) < 0) + mpz_neg (quotient.val, quotient.val); + + return quotient; +} + +/* See gmp-utils.h. */ + +void +gdb_mpq::read_fixed_point (const gdb_byte *buf, int len, + enum bfd_endian byte_order, bool unsigned_p, + const gdb_mpq &scaling_factor) +{ + gdb_mpz vz; + vz.read (buf, len, byte_order, unsigned_p); + + mpq_set_z (val, vz.val); + mpq_mul (val, val, scaling_factor.val); +} + +/* See gmp-utils.h. */ + +void +gdb_mpq::write_fixed_point (gdb_byte *buf, int len, + enum bfd_endian byte_order, bool unsigned_p, + const gdb_mpq &scaling_factor) const +{ + gdb_mpq unscaled (val); + + mpq_div (unscaled.val, unscaled.val, scaling_factor.val); + + gdb_mpz unscaled_z = unscaled.get_rounded (); + unscaled_z.write (buf, len, byte_order, unsigned_p); +} + +/* A wrapper around xrealloc that we can then register with GMP + as the "realloc" function. */ + +static void * +xrealloc_for_gmp (void *ptr, size_t old_size, size_t new_size) +{ + return xrealloc (ptr, new_size); +} + +/* A wrapper around xfree that we can then register with GMP + as the "free" function. */ + +static void +xfree_for_gmp (void *ptr, size_t size) +{ + xfree (ptr); +} + +void _initialize_gmp_utils (); + +void +_initialize_gmp_utils () +{ + /* Tell GMP to use GDB's memory management routines. */ + mp_set_memory_functions (xmalloc, xrealloc_for_gmp, xfree_for_gmp); +} diff --git a/gdb/gmp-utils.h b/gdb/gmp-utils.h new file mode 100644 index 00000000000..1214b645367 --- /dev/null +++ b/gdb/gmp-utils.h @@ -0,0 +1,282 @@ +/* Miscellaneous routines making it easier to use GMP within GDB's framework. + + 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 . */ + +#ifndef GMP_UTILS_H +#define GMP_UTILS_H + +#include "defs.h" + +/* Include and ahead of , so as to get + access to GMP's various formatting functions. */ +#include +#include +#include +#include "gdbsupport/traits.h" + +/* Same as gmp_asprintf, but returning a convenient wrapper type. */ + +gdb::unique_xmalloc_ptr gmp_string_asprintf (const char *fmt, ...); + +/* A class to make it easier to use GMP's mpz_t values within GDB. */ + +struct gdb_mpz +{ + mpz_t val; + + /* Constructors. */ + gdb_mpz () { mpz_init (val); } + + explicit gdb_mpz (const mpz_t &from_val) + { + mpz_init (val); + mpz_set (val, from_val); + } + + gdb_mpz (const gdb_mpz &from) + { + mpz_init (val); + mpz_set (val, from.val); + } + + /* Initialize using the given integral value. + + The main advantage of this method is that it handles both signed + and unsigned types, with no size restriction. */ + template>> + explicit gdb_mpz (T src) + { + mpz_init (val); + set (src); + } + + explicit gdb_mpz (gdb_mpz &&from) + { + mpz_init (val); + mpz_swap (val, from.val); + } + + + gdb_mpz &operator= (const gdb_mpz &from) + { + mpz_set (val, from.val); + return *this; + } + + gdb_mpz &operator= (gdb_mpz &&other) + { + mpz_swap (val, other.val); + return *this; + } + + template>> + gdb_mpz &operator= (T src) + { + set (src); + return *this; + } + + /* Convert VAL to an integer of the given type. + + The return type can signed or unsigned, with no size restriction. */ + template T as_integer () const; + + /* Set VAL by importing the number stored in the byte buffer (BUF), + given its size (LEN) and BYTE_ORDER. + + UNSIGNED_P indicates whether the number has an unsigned type. */ + void read (const gdb_byte *buf, int len, enum bfd_endian byte_order, + bool unsigned_p); + + /* Write VAL into BUF as a LEN-bytes number with the given BYTE_ORDER. + + UNSIGNED_P indicates whether the number has an unsigned type. */ + void write (gdb_byte *buf, int len, enum bfd_endian byte_order, + bool unsigned_p) const; + + /* Return a string containing VAL. */ + gdb::unique_xmalloc_ptr str () const + { return gmp_string_asprintf ("%Zd", val); } + + /* The destructor. */ + ~gdb_mpz () { mpz_clear (val); } + +private: + + /* Helper template for constructor and operator=. */ + template void set (T src); +}; + +/* A class to make it easier to use GMP's mpq_t values within GDB. */ + +struct gdb_mpq +{ + mpq_t val; + + /* Constructors. */ + gdb_mpq () { mpq_init (val); } + + explicit gdb_mpq (const mpq_t &from_val) + { + mpq_init (val); + mpq_set (val, from_val); + } + + gdb_mpq (const gdb_mpq &from) + { + mpq_init (val); + mpq_set (val, from.val); + } + + explicit gdb_mpq (gdb_mpq &&from) + { + mpq_init (val); + mpq_swap (val, from.val); + } + + /* Copy assignment operator. */ + gdb_mpq &operator= (const gdb_mpq &from) + { + mpq_set (val, from.val); + return *this; + } + + gdb_mpq &operator= (gdb_mpq &&from) + { + mpq_swap (val, from.val); + return *this; + } + + /* Return a string representing VAL as " / ". */ + gdb::unique_xmalloc_ptr str () const + { return gmp_string_asprintf ("%Qd", val); } + + /* Return VAL rounded to the nearest integer. */ + gdb_mpz get_rounded () const; + + /* Set VAL from the contents of the given buffer (BUF), which + contains the unscaled value of a fixed point type object + with the given size (LEN) and byte order (BYTE_ORDER). + + UNSIGNED_P indicates whether the number has an unsigned type. + SCALING_FACTOR is the scaling factor to apply after having + read the unscaled value from our buffer. */ + void read_fixed_point (const gdb_byte *buf, int len, + enum bfd_endian byte_order, bool unsigned_p, + const gdb_mpq &scaling_factor); + + /* Write VAL into BUF as a LEN-bytes fixed point value following + the given BYTE_ORDER. + + UNSIGNED_P indicates whether the number has an unsigned type. + SCALING_FACTOR is the scaling factor to apply before writing + the unscaled value to our buffer. */ + void write_fixed_point (gdb_byte *buf, int len, + enum bfd_endian byte_order, bool unsigned_p, + const gdb_mpq &scaling_factor) const; + + /* The destructor. */ + ~gdb_mpq () { mpq_clear (val); } +}; + +/* A class to make it easier to use GMP's mpf_t values within GDB. + + Should MPFR become a required dependency, we should probably + drop this class in favor of using MPFR. */ + +struct gdb_mpf +{ + mpf_t val; + + /* Constructors. */ + gdb_mpf () { mpf_init (val); } + + DISABLE_COPY_AND_ASSIGN (gdb_mpf); + + /* Set VAL from the contents of the given buffer (BUF), which + contains the unscaled value of a fixed point type object + with the given size (LEN) and byte order (BYTE_ORDER). + + UNSIGNED_P indicates whether the number has an unsigned type. + SCALING_FACTOR is the scaling factor to apply after having + read the unscaled value from our buffer. */ + void read_fixed_point (const gdb_byte *buf, int len, + enum bfd_endian byte_order, bool unsigned_p, + const gdb_mpq &scaling_factor) + { + gdb_mpq tmp_q; + + tmp_q.read_fixed_point (buf, len, byte_order, unsigned_p, scaling_factor); + mpf_set_q (val, tmp_q.val); + } + + /* The destructor. */ + ~gdb_mpf () { mpf_clear (val); } +}; + +/* See declaration above. */ + +template +void +gdb_mpz::set (T src) +{ + mpz_import (val, 1 /* count */, -1 /* order */, + sizeof (T) /* size */, 0 /* endian (0 = native) */, + 0 /* nails */, &src /* op */); + if (std::is_signed::value && src < 0) + { + /* mpz_import does not handle the sign, so our value was imported + as an unsigned. Adjust that imported value so as to make it + the correct negative value. */ + gdb_mpz neg_offset; + + mpz_ui_pow_ui (neg_offset.val, 2, sizeof (T) * HOST_CHAR_BIT); + mpz_sub (val, val, neg_offset.val); + } +} + +/* See declaration above. */ + +template +T +gdb_mpz::as_integer () const +{ + /* Initialize RESULT, because mpz_export only write the minimum + number of bytes, including none if our value is zero! */ + T result = 0; + + gdb_mpz exported_val (val); + if (std::is_signed::value && mpz_cmp_ui (val, 0) < 0) + { + /* We want to use mpz_export to set the return value, but + this function does not handle the sign. So give exported_val + a value which is at the same time positive, and has the same + bit representation as our negative value. */ + gdb_mpz neg_offset; + + mpz_ui_pow_ui (neg_offset.val, 2, sizeof (T) * HOST_CHAR_BIT); + mpz_add (exported_val.val, exported_val.val, neg_offset.val); + } + + mpz_export (&result, NULL /* count */, -1 /* order */, + sizeof (T) /* size */, 0 /* endian (0 = native) */, + 0 /* nails */, exported_val.val); + return result; +} + +#endif diff --git a/gdb/unittests/gmp-utils-selftests.c b/gdb/unittests/gmp-utils-selftests.c new file mode 100644 index 00000000000..e8c3c5c1cb5 --- /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 . */ + +#include "gmp-utils.h" +#include "gdbsupport/selftest.h" + +#include + +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 () == 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 () == l_expected); + + if (i >= 0) + { + ul_expected = (ULONGEST) i; + mpz_set_ui (v.val, i); + SELF_CHECK (v.as_integer () == 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 () == l_expected); + SELF_CHECK (v.as_integer () == 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 () == 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 +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::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 +T +write_and_extract (T val, int buf_len, enum bfd_endian byte_order) +{ + gdb_mpz v (val); + + SELF_CHECK (v.as_integer () == val); + + gdb_byte *buf = (gdb_byte *) alloca (buf_len); + v.write (buf, buf_len, byte_order, !std::is_signed::value); + + return extract_integer (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); +} -- 2.30.2