void-packages/srcpkgs/sagemath/patches/36046-fix_memory_leak.patch

741 lines
18 KiB
Diff

diff --git a/src/sage/ext_data/valgrind/valgrind-python.supp b/src/sage/ext_data/valgrind/valgrind-python.supp
new file mode 100644
index 00000000000..16aa2858484
--- /dev/null
+++ b/src/sage/ext_data/valgrind/valgrind-python.supp
@@ -0,0 +1,480 @@
+# From the CPython repository with the suppressions for _PyObject_Free
+# and _PyObject_Realloc enabled. See the upstream suppression file for
+# details:
+#
+# https://github.com/python/cpython/blob/main/Misc/valgrind-python.supp
+
+# all tool names: Addrcheck,Memcheck,cachegrind,helgrind,massif
+{
+ ADDRESS_IN_RANGE/Invalid read of size 4
+ Memcheck:Addr4
+ fun:address_in_range
+}
+
+{
+ ADDRESS_IN_RANGE/Invalid read of size 4
+ Memcheck:Value4
+ fun:address_in_range
+}
+
+{
+ ADDRESS_IN_RANGE/Invalid read of size 8 (x86_64 aka amd64)
+ Memcheck:Value8
+ fun:address_in_range
+}
+
+{
+ ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value
+ Memcheck:Cond
+ fun:address_in_range
+}
+
+#
+# Leaks (including possible leaks)
+# Hmmm, I wonder if this masks some real leaks. I think it does.
+# Will need to fix that.
+#
+
+{
+ Suppress leaking the GIL after a fork.
+ Memcheck:Leak
+ fun:malloc
+ fun:PyThread_allocate_lock
+ fun:PyEval_ReInitThreads
+}
+
+{
+ Suppress leaking the autoTLSkey. This looks like it shouldn't leak though.
+ Memcheck:Leak
+ fun:malloc
+ fun:PyThread_create_key
+ fun:_PyGILState_Init
+ fun:Py_InitializeEx
+ fun:Py_Main
+}
+
+{
+ Hmmm, is this a real leak or like the GIL?
+ Memcheck:Leak
+ fun:malloc
+ fun:PyThread_ReInitTLS
+}
+
+{
+ Handle PyMalloc confusing valgrind (possibly leaked)
+ Memcheck:Leak
+ fun:realloc
+ fun:_PyObject_GC_Resize
+ fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING
+}
+
+{
+ Handle PyMalloc confusing valgrind (possibly leaked)
+ Memcheck:Leak
+ fun:malloc
+ fun:_PyObject_GC_New
+ fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING
+}
+
+{
+ Handle PyMalloc confusing valgrind (possibly leaked)
+ Memcheck:Leak
+ fun:malloc
+ fun:_PyObject_GC_NewVar
+ fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING
+}
+
+#
+# Non-python specific leaks
+#
+
+{
+ Handle pthread issue (possibly leaked)
+ Memcheck:Leak
+ fun:calloc
+ fun:allocate_dtv
+ fun:_dl_allocate_tls_storage
+ fun:_dl_allocate_tls
+}
+
+{
+ Handle pthread issue (possibly leaked)
+ Memcheck:Leak
+ fun:memalign
+ fun:_dl_allocate_tls_storage
+ fun:_dl_allocate_tls
+}
+
+{
+ ADDRESS_IN_RANGE/Invalid read of size 4
+ Memcheck:Addr4
+ fun:_PyObject_Free
+}
+
+{
+ ADDRESS_IN_RANGE/Invalid read of size 4
+ Memcheck:Value4
+ fun:_PyObject_Free
+}
+
+{
+ ADDRESS_IN_RANGE/Use of uninitialised value of size 8
+ Memcheck:Addr8
+ fun:_PyObject_Free
+}
+
+{
+ ADDRESS_IN_RANGE/Use of uninitialised value of size 8
+ Memcheck:Value8
+ fun:_PyObject_Free
+}
+
+{
+ ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value
+ Memcheck:Cond
+ fun:_PyObject_Free
+}
+
+{
+ ADDRESS_IN_RANGE/Invalid read of size 4
+ Memcheck:Addr4
+ fun:_PyObject_Realloc
+}
+
+{
+ ADDRESS_IN_RANGE/Invalid read of size 4
+ Memcheck:Value4
+ fun:_PyObject_Realloc
+}
+
+{
+ ADDRESS_IN_RANGE/Use of uninitialised value of size 8
+ Memcheck:Addr8
+ fun:_PyObject_Realloc
+}
+
+{
+ ADDRESS_IN_RANGE/Use of uninitialised value of size 8
+ Memcheck:Value8
+ fun:_PyObject_Realloc
+}
+
+{
+ ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value
+ Memcheck:Cond
+ fun:_PyObject_Realloc
+}
+
+###
+### All the suppressions below are for errors that occur within libraries
+### that Python uses. The problems to not appear to be related to Python's
+### use of the libraries.
+###
+
+{
+ Generic ubuntu ld problems
+ Memcheck:Addr8
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+ obj:/lib/ld-2.4.so
+}
+
+{
+ Generic gentoo ld problems
+ Memcheck:Cond
+ obj:/lib/ld-2.3.4.so
+ obj:/lib/ld-2.3.4.so
+ obj:/lib/ld-2.3.4.so
+ obj:/lib/ld-2.3.4.so
+}
+
+{
+ DBM problems, see test_dbm
+ Memcheck:Param
+ write(buf)
+ fun:write
+ obj:/usr/lib/libdb1.so.2
+ obj:/usr/lib/libdb1.so.2
+ obj:/usr/lib/libdb1.so.2
+ obj:/usr/lib/libdb1.so.2
+ fun:dbm_close
+}
+
+{
+ DBM problems, see test_dbm
+ Memcheck:Value8
+ fun:memmove
+ obj:/usr/lib/libdb1.so.2
+ obj:/usr/lib/libdb1.so.2
+ obj:/usr/lib/libdb1.so.2
+ obj:/usr/lib/libdb1.so.2
+ fun:dbm_store
+ fun:dbm_ass_sub
+}
+
+{
+ DBM problems, see test_dbm
+ Memcheck:Cond
+ obj:/usr/lib/libdb1.so.2
+ obj:/usr/lib/libdb1.so.2
+ obj:/usr/lib/libdb1.so.2
+ fun:dbm_store
+ fun:dbm_ass_sub
+}
+
+{
+ DBM problems, see test_dbm
+ Memcheck:Cond
+ fun:memmove
+ obj:/usr/lib/libdb1.so.2
+ obj:/usr/lib/libdb1.so.2
+ obj:/usr/lib/libdb1.so.2
+ obj:/usr/lib/libdb1.so.2
+ fun:dbm_store
+ fun:dbm_ass_sub
+}
+
+{
+ GDBM problems, see test_gdbm
+ Memcheck:Param
+ write(buf)
+ fun:write
+ fun:gdbm_open
+
+}
+
+{
+ Uninitialised byte(s) false alarm, see bpo-35561
+ Memcheck:Param
+ epoll_ctl(event)
+ fun:epoll_ctl
+ fun:pyepoll_internal_ctl
+}
+
+{
+ ZLIB problems, see test_gzip
+ Memcheck:Cond
+ obj:/lib/libz.so.1.2.3
+ obj:/lib/libz.so.1.2.3
+ fun:deflate
+}
+
+{
+ Avoid problems w/readline doing a putenv and leaking on exit
+ Memcheck:Leak
+ fun:malloc
+ fun:xmalloc
+ fun:sh_set_lines_and_columns
+ fun:_rl_get_screen_size
+ fun:_rl_init_terminal_io
+ obj:/lib/libreadline.so.4.3
+ fun:rl_initialize
+}
+
+# Valgrind emits "Conditional jump or move depends on uninitialised value(s)"
+# false alarms on GCC builtin strcmp() function. The GCC code is correct.
+#
+# Valgrind bug: https://bugs.kde.org/show_bug.cgi?id=264936
+{
+ bpo-38118: Valgrind emits false alarm on GCC builtin strcmp()
+ Memcheck:Cond
+ fun:PyUnicode_Decode
+}
+
+
+###
+### These occur from somewhere within the SSL, when running
+### test_socket_sll. They are too general to leave on by default.
+###
+###{
+### somewhere in SSL stuff
+### Memcheck:Cond
+### fun:memset
+###}
+###{
+### somewhere in SSL stuff
+### Memcheck:Value4
+### fun:memset
+###}
+###
+###{
+### somewhere in SSL stuff
+### Memcheck:Cond
+### fun:MD5_Update
+###}
+###
+###{
+### somewhere in SSL stuff
+### Memcheck:Value4
+### fun:MD5_Update
+###}
+
+# Fedora's package "openssl-1.0.1-0.1.beta2.fc17.x86_64" on x86_64
+# See http://bugs.python.org/issue14171
+{
+ openssl 1.0.1 prng 1
+ Memcheck:Cond
+ fun:bcmp
+ fun:fips_get_entropy
+ fun:FIPS_drbg_instantiate
+ fun:RAND_init_fips
+ fun:OPENSSL_init_library
+ fun:SSL_library_init
+ fun:init_hashlib
+}
+
+{
+ openssl 1.0.1 prng 2
+ Memcheck:Cond
+ fun:fips_get_entropy
+ fun:FIPS_drbg_instantiate
+ fun:RAND_init_fips
+ fun:OPENSSL_init_library
+ fun:SSL_library_init
+ fun:init_hashlib
+}
+
+{
+ openssl 1.0.1 prng 3
+ Memcheck:Value8
+ fun:_x86_64_AES_encrypt_compact
+ fun:AES_encrypt
+}
+
+#
+# All of these problems come from using test_socket_ssl
+#
+{
+ from test_socket_ssl
+ Memcheck:Cond
+ fun:BN_bin2bn
+}
+
+{
+ from test_socket_ssl
+ Memcheck:Cond
+ fun:BN_num_bits_word
+}
+
+{
+ from test_socket_ssl
+ Memcheck:Value4
+ fun:BN_num_bits_word
+}
+
+{
+ from test_socket_ssl
+ Memcheck:Cond
+ fun:BN_mod_exp_mont_word
+}
+
+{
+ from test_socket_ssl
+ Memcheck:Cond
+ fun:BN_mod_exp_mont
+}
+
+{
+ from test_socket_ssl
+ Memcheck:Param
+ write(buf)
+ fun:write
+ obj:/usr/lib/libcrypto.so.0.9.7
+}
+
+{
+ from test_socket_ssl
+ Memcheck:Cond
+ fun:RSA_verify
+}
+
+{
+ from test_socket_ssl
+ Memcheck:Value4
+ fun:RSA_verify
+}
+
+{
+ from test_socket_ssl
+ Memcheck:Value4
+ fun:DES_set_key_unchecked
+}
+
+{
+ from test_socket_ssl
+ Memcheck:Value4
+ fun:DES_encrypt2
+}
+
+{
+ from test_socket_ssl
+ Memcheck:Cond
+ obj:/usr/lib/libssl.so.0.9.7
+}
+
+{
+ from test_socket_ssl
+ Memcheck:Value4
+ obj:/usr/lib/libssl.so.0.9.7
+}
+
+{
+ from test_socket_ssl
+ Memcheck:Cond
+ fun:BUF_MEM_grow_clean
+}
+
+{
+ from test_socket_ssl
+ Memcheck:Cond
+ fun:memcpy
+ fun:ssl3_read_bytes
+}
+
+{
+ from test_socket_ssl
+ Memcheck:Cond
+ fun:SHA1_Update
+}
+
+{
+ from test_socket_ssl
+ Memcheck:Value4
+ fun:SHA1_Update
+}
+
+{
+ test_buffer_non_debug
+ Memcheck:Addr4
+ fun:PyUnicodeUCS2_FSConverter
+}
+
+{
+ test_buffer_non_debug
+ Memcheck:Addr4
+ fun:PyUnicode_FSConverter
+}
+
+{
+ wcscmp_false_positive
+ Memcheck:Addr8
+ fun:wcscmp
+ fun:_PyOS_GetOpt
+ fun:Py_Main
+ fun:main
+}
+
+# Additional suppressions for the unified decimal tests:
+{
+ test_decimal
+ Memcheck:Addr4
+ fun:PyUnicodeUCS2_FSConverter
+}
+
+{
+ test_decimal2
+ Memcheck:Addr4
+ fun:PyUnicode_FSConverter
+}
+
diff --git a/src/sage/symbolic/ginac/numeric.cpp b/src/sage/symbolic/ginac/numeric.cpp
index b40ed64edb5..8c55861c147 100644
--- a/src/sage/symbolic/ginac/numeric.cpp
+++ b/src/sage/symbolic/ginac/numeric.cpp
@@ -1576,6 +1576,62 @@ const numeric numeric::div(const numeric &other) const {
}
}
+
+// Compute `a^b` as an integer, where a is an integer. Assign to ``res``` if it is integral, or return ``false``.
+// The nonnegative real root is taken for even denominators. To be used inside numeric::integer_rational_power,
+// to handle the special case of integral ``a``.
+bool integer_rational_power_of_mpz(
+ numeric& res,
+ const numeric& a,
+ const numeric& b
+) {
+ if (a.t != MPZ)
+ throw std::runtime_error("integer_rational_power_of_mpz: bad input");
+ mpz_t z;
+ mpz_init(z);
+ mpz_set_ui(z, 0);
+ int sgn = mpz_sgn(a.v._bigint);
+ if (mpz_cmp_ui(a.v._bigint, 1) == 0
+ or mpz_cmp_ui(mpq_numref(b.v._bigrat), 0) == 0)
+ mpz_set_ui(z, 1);
+ else if (sgn == 0) {
+ res = *_num0_p;
+ mpz_clear(z);
+ return true;
+ }
+ else if (sgn < 0 and mpz_cmp_ui(mpq_denref(b.v._bigrat), 1)) {
+ mpz_clear(z);
+ return false;
+ } else {
+ if (not mpz_fits_ulong_p(mpq_numref(b.v._bigrat))
+ or not mpz_fits_ulong_p(mpq_denref(b.v._bigrat))) {
+ // too big to take roots/powers
+ mpz_clear(z);
+ return false;
+ }
+ if (mpz_cmp_ui(mpq_denref(b.v._bigrat), 2) == 0) {
+ if (mpz_perfect_square_p(a.v._bigint)) {
+ mpz_sqrt(z, a.v._bigint);
+ } else {
+ mpz_clear(z);
+ return false;
+ }
+ }
+ else {
+ bool exact = mpz_root(z, a.v._bigint,
+ mpz_get_ui(mpq_denref(b.v._bigrat)));
+ if (not exact) {
+ mpz_clear(z);
+ return false;
+ }
+ }
+ mpz_pow_ui(z, z, mpz_get_ui(mpq_numref(b.v._bigrat)));
+ }
+ res = numeric(z); // transfers ownership, no mpz_clear
+ return true;
+}
+
+
// Compute `a^b` as an integer, if it is integral, or return ``false``.
// The nonnegative real root is taken for even denominators.
bool numeric::integer_rational_power(numeric& res,
@@ -1598,13 +1654,12 @@ bool numeric::integer_rational_power(numeric& res,
if (a.v._long < 0
and mpz_cmp_ui(mpq_denref(b.v._bigrat), 1))
return false;
- long z;
if (not mpz_fits_ulong_p(mpq_numref(b.v._bigrat))
or not mpz_fits_ulong_p(mpq_denref(b.v._bigrat)))
// too big to take roots/powers
return false;
if (b.is_equal(*_num1_2_p)) {
- z = std::lround(std::sqrt(a.v._long));
+ long z = std::lround(std::sqrt(a.v._long));
if (a.v._long == z*z) {
res = numeric(z);
return true;
@@ -1613,44 +1668,11 @@ bool numeric::integer_rational_power(numeric& res,
}
return integer_rational_power(res, a.to_bigint(), b);
}
- if (a.t != MPZ)
- throw std::runtime_error("integer_rational_power: bad input");
- int sgn = mpz_sgn(a.v._bigint);
- mpz_t z;
- mpz_init(z);
- mpz_set_ui(z, 0);
- if (mpz_cmp_ui(a.v._bigint, 1) == 0
- or mpz_cmp_ui(mpq_numref(b.v._bigrat), 0) == 0)
- mpz_set_ui(z, 1);
- else if (sgn == 0) {
- res = *_num0_p;
- return true;
- }
- else if (sgn < 0 and mpz_cmp_ui(mpq_denref(b.v._bigrat), 1))
- return false;
- else {
- if (not mpz_fits_ulong_p(mpq_numref(b.v._bigrat))
- or not mpz_fits_ulong_p(mpq_denref(b.v._bigrat)))
- // too big to take roots/powers
- return false;
- if (mpz_cmp_ui(mpq_denref(b.v._bigrat), 2) == 0) {
- if (mpz_perfect_square_p(a.v._bigint))
- mpz_sqrt(z, a.v._bigint);
- else
- return false;
- }
- else {
- bool exact = mpz_root(z, a.v._bigint,
- mpz_get_ui(mpq_denref(b.v._bigrat)));
- if (not exact)
- return false;
- }
- mpz_pow_ui(z, z, mpz_get_ui(mpq_numref(b.v._bigrat)));
- }
- res = numeric(z);
- return true;
+ // otherwise: a is integer
+ return integer_rational_power_of_mpz(res, a, b);
}
+
// for a^b return c,d such that a^b = c*d^b
// only for MPZ/MPQ base and MPQ exponent
void rational_power_parts(const numeric& a_orig, const numeric& b_orig,
diff --git a/src/sage/tests/memcheck/__init__.py b/src/sage/tests/memcheck/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/sage/tests/memcheck/run_tests.py b/src/sage/tests/memcheck/run_tests.py
new file mode 100644
index 00000000000..6ff4503a81b
--- /dev/null
+++ b/src/sage/tests/memcheck/run_tests.py
@@ -0,0 +1,24 @@
+import types
+
+
+def run_tests() -> None:
+ """
+ Run all memcheck tests
+ """
+ from sage.tests.memcheck import symbolic_expression
+ run_tests_in_module(symbolic_expression)
+
+
+def run_tests_in_module(mod: types.ModuleType) -> None:
+ """
+ Run all memcheck tests in the given module
+ """
+ for entry in dir(mod):
+ if not entry.startswith('test_'):
+ continue
+ test_func = getattr(mod, entry)
+ test_func()
+
+
+if __name__ == '__main__':
+ run_tests()
diff --git a/src/sage/tests/memcheck/run_tests_in_valgrind.py b/src/sage/tests/memcheck/run_tests_in_valgrind.py
new file mode 100644
index 00000000000..df5ad0e92b2
--- /dev/null
+++ b/src/sage/tests/memcheck/run_tests_in_valgrind.py
@@ -0,0 +1,35 @@
+"""
+Launch valgrind and run the memory leak tests
+
+
+From the commandline, run
+
+ sage -python -m sage.tests.memcheck.run_tests_in_valgrind
+
+to launch valgrind and execute the memory leak tests. Requires valgrind
+to be installed. Alternatively, run as a unit test:
+
+ sage: from sage.tests.memcheck.run_tests_in_valgrind import run_tests_in_valgrind
+ sage: run_tests_in_valgrind() # optional - valgrind
+"""
+
+import subprocess
+
+
+def run_tests_in_valgrind() -> None:
+ """
+ Run the sage.tests.memcheck.run_tests module inside valgrind
+ """
+ subprocess.check_call([
+ 'valgrind',
+ '--suppressions=src/sage/ext_data/valgrind/valgrind-python.supp',
+ '--show-possibly-lost=no',
+ '--show-reachable=no',
+ './venv/bin/python',
+ '-m',
+ 'sage.tests.memcheck.run_tests'
+ ])
+
+
+if __name__ == '__main__':
+ run_tests_in_valgrind()
diff --git a/src/sage/tests/memcheck/symbolic_expression.py b/src/sage/tests/memcheck/symbolic_expression.py
new file mode 100644
index 00000000000..52182fbe62d
--- /dev/null
+++ b/src/sage/tests/memcheck/symbolic_expression.py
@@ -0,0 +1,11 @@
+from sage.tests.memcheck.verify_no_leak import verify_no_leak
+
+
+def test_sqrt_sqrt_2() -> None:
+ from sage.misc.functional import sqrt
+ T2 = sqrt(2)
+
+ def sqrt_T2() -> None:
+ sqrt(T2)
+
+ verify_no_leak(sqrt_T2)
diff --git a/src/sage/tests/memcheck/verify_no_leak.py b/src/sage/tests/memcheck/verify_no_leak.py
new file mode 100644
index 00000000000..89ca90cf89c
--- /dev/null
+++ b/src/sage/tests/memcheck/verify_no_leak.py
@@ -0,0 +1,27 @@
+from typing import Tuple, Sequence, List, Callable, Any
+import valgrind
+
+
+def verify_no_leak(callback: Callable[[], Any],
+ repeat: int = 10000,
+ fuzzy: int = 10,
+ ) -> None:
+ """
+ Verify that the callback does not generate new definitely lost blocks
+
+ Raises an assertion if the callback leaks memory
+ """
+ callback() # warm_up
+ initial_blocks = (0, 0, 0, 0)
+ valgrind.memcheck_do_leak_check()
+ initial_blocks = valgrind.memcheck_count_leak_blocks()
+ for _ in range(repeat):
+ callback()
+ valgrind.memcheck_do_leak_check()
+ leak_blocks = valgrind.memcheck_count_leak_blocks()
+ leak = leak_blocks[0] - initial_blocks[0]
+ if leak < repeat - fuzzy:
+ return # callback did not leak at least once per call
+ blocks = round(leak / repeat, 2)
+ message = f'{callback} leaked {blocks} block on average ({repeat} iterations)'
+ raise AssertionError(message)