Steps To Reproduce
This is #10543 ("Echelon form over QQ is mutable"), which was fixed for dense rational matrices only. Matrix_rational_sparse was never updated and still has the bug — with the extra consequence, not present in the original report, that the mutable result is shared with the cache, so modifying it corrupts the source matrix.
Matrix_rational_sparse.echelon_form caches its result and returns it mutable. The caller can therefore modify the matrix that the cache hands out, and every later call returns the corrupted object:
sage: A = matrix(QQ, [[1, 2], [3, 4]], sparse=True)
sage: E = A.echelon_form()
sage: E.is_mutable()
True
sage: E[0, 0] = 0
sage: A.echelon_form()
[0 0]
[0 1] # not the echelon form of A
The dense class does not have this problem, because it calls set_immutable() on the result before caching it:
sage: matrix(QQ, [[1, 2], [3, 4]]).echelon_form().is_mutable()
False
Expected Behavior
echelon_form() should return an immutable matrix, as it does for dense matrices over QQ (since #10543) and for matrices over ZZ. A caller who wants to modify the result should have to copy it.
Actual Behavior
The sparse result is mutable and shared with the cache, so mutating it silently corrupts the source matrix's cached echelon form (and its cached pivots).
Additional Information
In src/sage/matrix/matrix_rational_sparse.pyx, echelon_form ends with
E, pivots = self._echelon_form_multimodular(height_guess, proof=proof)
self.cache(label, E)
self.cache('pivots', pivots)
return E
with no E.set_immutable(), whereas Matrix_rational_dense.echelon_form does call it.
There is a second, related leak of the same invariant a few lines up: when self is already in echelon form, the sparse method returns self — which may be mutable — instead of an immutable copy.
Found while working on #42417 (multimodular echelon form over QQ); this bug is independent of that PR and develop behaves the same way, so it is filed separately rather than fixed there.
Environment
- OS: Linux (Ubuntu, WSL2)
- Sage Version: 10.10.beta5
Checklist
Steps To Reproduce
This is #10543 ("Echelon form over QQ is mutable"), which was fixed for dense rational matrices only.
Matrix_rational_sparsewas never updated and still has the bug — with the extra consequence, not present in the original report, that the mutable result is shared with the cache, so modifying it corrupts the source matrix.Matrix_rational_sparse.echelon_formcaches its result and returns it mutable. The caller can therefore modify the matrix that the cache hands out, and every later call returns the corrupted object:The dense class does not have this problem, because it calls
set_immutable()on the result before caching it:Expected Behavior
echelon_form()should return an immutable matrix, as it does for dense matrices overQQ(since #10543) and for matrices overZZ. A caller who wants to modify the result should have to copy it.Actual Behavior
The sparse result is mutable and shared with the cache, so mutating it silently corrupts the source matrix's cached echelon form (and its cached
pivots).Additional Information
In
src/sage/matrix/matrix_rational_sparse.pyx,echelon_formends withwith no
E.set_immutable(), whereasMatrix_rational_dense.echelon_formdoes call it.There is a second, related leak of the same invariant a few lines up: when
selfis already in echelon form, the sparse method returnsself— which may be mutable — instead of an immutable copy.Found while working on #42417 (multimodular echelon form over
QQ); this bug is independent of that PR anddevelopbehaves the same way, so it is filed separately rather than fixed there.Environment
Checklist