Repository: scipy/scipy

Issue:
freeing non-valid pointer in ndimage.rank_filter
<!-- 
Thank you for taking the time to report a SciPy issue.
Please describe the issue in detail, and for bug reports
fill in the fields below. You can delete the sections that 
don't apply to your issue. You can view the final output
by clicking the preview button above.
-->

Python crashing due to SciPy freeing a pointer that does not point to allocated memory.

### Reproducing code example:
<!-- 
If you place your code between the triple backticks below, 
it will be marked as a code block automatically 
-->


```python
$ ipython
Python 3.5.5 | packaged by conda-forge | (default, Apr  6 2018, 13:43:56) 
Type 'copyright', 'credits' or 'license' for more information
IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import numpy as np

In [2]: import scipy.ndimage as spimg

In [3]: a = np.random.random((10, 20, 30))

In [4]: r_1 = spimg.rank_filter(a, 0.5, footprint=np.ones((1, 1, 10), dtype=bool))
python(79733,0x7fff78c69000) malloc: *** error for object 0x7fdf9b146468: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
Abort trap: 6
```

### Error message:
<!-- If any, paste the *full* error message inside a code block
as above (starting from line Traceback)
-->

```gdb
python(79733,0x7fff78c69000) malloc: *** error for object 0x7fdf9b146468: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
Abort trap: 6
```

### Scipy/Numpy/Python version information:

```python
In [1]: import sys, scipy, numpy; print(scipy.__version__, numpy.__version__, sys.version_info)
1.1.0 1.15.1 sys.version_info(major=3, minor=5, micro=5, releaselevel='final', serial=0)
```


Discussion:
Does it happen also when you use an integer for rank and either size or footprint ?
Hello, I am a newbie trying to triage this crash, I could trace it down to the Py_RankFilter function in the nd_image.c. However I couldn't debug since pdb gave up after the call to PyCFunction. Can anyone please give me some pointers on how to debug in such scenarios, the wiki page and google search didn't help.
> Does it happen also when you use an integer for rank and either size or footprint ?

I did not see the crash when rank is integer
Sure. Happy to. This should be a good problem for learning (or becoming more familiar with) some low level debugging tools if that is interesting to you. Are you familiar with `gdb` or `valgrind`?
> Sure. Happy to. This should be a good problem for learning (or becoming more familiar with) some low level debugging tools if that is interesting to you. Are you familiar with `gdb` or `valgrind`?

Yes, I have used both. As per my understanding to debug a C program, we need the debug symbols and the executable. So should I expect both to be available in some location after I install with "python setup.py install"?
> Sure. Happy to. This should be a good problem for learning (or becoming more familiar with) some low level debugging tools if that is interesting to you. 

Thank you very much! Yes, I am interested to learn some debugging tools.
Right, so this likely requires a debug build of Python to be installed. I'm not sure how SciPy handles debug symbols (maybe a core dev can chime in), but you will want to make sure that the appropriate compiler flags are passed (i.e. `-DNDEBUG` should not be in the compiler flags, also `-g` should be present on `gcc` or similar flag for other compilers).

Valgrind can be reasonably effective even without debug symbols. Given it is a memory issue, it's also a good choice for this problem. GDB can be used to further isolate the line if Valgrind doesn't already point you to the fix. So would probably start by running a short reproducer script with Valgrind and see what information it gives.
Before we jump to the internals, this function should not allow nonintegers to be passed to lower levels since 1.5 rank is not meaningful. 

Don't take it as a discouraging comment please you can still work on it but we need a quick exit strategy anyways against floats regardless of what is happening at the C level.
Thank you for jumping in @ilayn , So the function _rank_filter in filters.py should have an error message, and return if rank is not integer? I am happy to make that fix :)

Resolving patch:
diff --git a/scipy/ndimage/filters.py b/scipy/ndimage/filters.py
--- a/scipy/ndimage/filters.py
+++ b/scipy/ndimage/filters.py
@@ -30,8 +30,9 @@
 
 from __future__ import division, print_function, absolute_import
 import warnings
-
+import numbers
 import numpy
+import operator
 from . import _ni_support
 from . import _nd_image
 from . import _ni_docstrings
@@ -1187,6 +1188,7 @@ def rank_filter(input, rank, size=None, footprint=None, output=None,
     >>> ax2.imshow(result)
     >>> plt.show()
     """
+    rank = operator.index(rank)
     return _rank_filter(input, rank, size, footprint, output, mode, cval,
                         origin, 'rank')
 


Test patch:
diff --git a/scipy/ndimage/tests/test_filters.py b/scipy/ndimage/tests/test_filters.py
--- a/scipy/ndimage/tests/test_filters.py
+++ b/scipy/ndimage/tests/test_filters.py
@@ -9,8 +9,8 @@
 from pytest import raises as assert_raises
 
 import scipy.ndimage as sndi
-from scipy.ndimage.filters import _gaussian_kernel1d
-
+from scipy.ndimage.filters import _gaussian_kernel1d, rank_filter
+from scipy._lib._numpy_compat import suppress_warnings
 
 def test_ticket_701():
     # Test generic filter sizes
@@ -427,3 +427,21 @@ def test_gaussian_filter():
     sigma = 1.0
     with assert_raises(RuntimeError):
         sndi.gaussian_filter(data,sigma)
+
+
+def test_rank_filter_noninteger_rank():
+    # regression test for issue 9388: ValueError for
+    # non integer rank when performing rank_filter
+    arr = np.random.random((10, 20, 30))
+    assert_raises(TypeError, rank_filter, arr, 0.5,
+                  footprint=np.ones((1, 1, 10), dtype=bool))
+
+
+def test_size_footprint_both_set():
+    # test for input validation, expect user warning when
+    # size and footprint is set
+    with suppress_warnings() as sup:
+        sup.filter(UserWarning,
+                   "ignoring size because footprint is set")
+        arr = np.random.random((10, 20, 30))
+        rank_filter(arr, 5, size=2, footprint=np.ones((1, 1, 10), dtype=bool))