Repository: pyvista/pyvista

Issue:
Check that arrays with padding are valid
### Describe the feature you would like to be added.

Hi !

I realized that `PolyData` can be initialized with `strips`, `faces` or `lines` that are invalid, as in the following examples :

```python
import pyvista as pv
import numpy as np

faces = np.array([3, 0, 1, 2, 3, 0])
points = np.random.rand(4, 3)
mesh = pv.PolyData(points, faces=faces)
print(mesh.faces)
>>> [   3    0    1    2    3    0   64 1009]

strips = np.array([5, 0, 1, 2])
points = np.random.rand(4, 3)
mesh = pv.PolyData(points, strips=strips)
print(mesh.strips)
>>> [   5    0    1    2   48 2529]

lines = np.array([5, 0, 1, 2])
points = np.random.rand(4, 3)
mesh = pv.PolyData(points, lines=lines)
print(mesh.lines)
>>> [   5    0    1    2   48 4609]
```

It seems that the memory is read outside the numpy arrays. I wrote a checker to assess that an array with padding is valid :

```python
def is_valid(list_padding):
    """Determines if a list with padding is valid."""
    if len(list_padding) == 0:
        return True
    
    offset = list_padding[0]
    if len(list_padding) < offset + 1:
        return False

    return is_valid(list_padding[offset + 1:])
```

Do you think it could be a good idea to integrate it somewhere ? (in [`validation`](https://github.com/pyvista/pyvista/pull/5100) ?) Let me know, I can work on the integration if requested.

### Links to VTK Documentation, Examples, or Class Definitions.

_No response_

### Pseudocode or Screenshots

```python
Insert code here
```
Or drag your screenshot here!

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/pyvista/pyvista/pull/5404?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyvista) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`276b50b`)](https://app.codecov.io/gh/pyvista/pyvista/commit/276b50b1cfff04ab7912a3e4e6acf72d28d1916f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyvista) 96.40% compared to head [(`be06e25`)](https://app.codecov.io/gh/pyvista/pyvista/pull/5404?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyvista) 89.80%.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #5404      +/-   ##
==========================================
- Coverage   96.40%   89.80%   -6.60%     
==========================================
  Files         134      134              
  Lines       22829    22831       +2     
==========================================
- Hits        22008    20504    -1504     
- Misses        821     2327    +1506     
```

</details>
darikg: 👍 Really appreciate all the time & effort that went into #5376. Very cool you found a zero-downside alternative. Trivial suggestion: maybe raise some sort of custom `CellSizeError` (or however you want to spell it) subclassing from `ValueError`? There's a bunch of prior art [here](https://github.com/pyvista/pyvista/blob/main/pyvista/core/errors.py).
Louis-Pujol: @darikg  I did the modification following your advice. Thanks
tkoyama010: Awesome job! It is ready to go after if @darikg approve this.
darikg: lgtm

Patch:
diff --git a/pyvista/core/cell.py b/pyvista/core/cell.py
index 7fd6f0d1..dbcdc852 100644
--- a/pyvista/core/cell.py
+++ b/pyvista/core/cell.py
@@ -11,6 +11,7 @@ from . import _vtk_core as _vtk
 from ._typing_core import IntMatrix, IntVector, NumpyIntArray
 from .celltype import CellType
 from .dataset import DataObject
+from .errors import CellSizeError
 from .utilities.cells import ncells_from_cells, numpy_to_idarr
 
 
@@ -637,6 +638,17 @@ class CellArray(_vtk.vtkCellArray):
                 n_cells = cells.shape[0]
 
         self.SetCells(n_cells, vtk_idarr)
+
+        # https://github.com/pyvista/pyvista/pull/5404
+        if self.cells.size != cells.size:
+            raise CellSizeError(
+                message=(
+                    f"Cell array size is invalid. Size ({cells.size}) does not"
+                    f" match expected size ({self.cells.size}). This is likely"
+                    " due to invalid connectivity array."
+                )
+            )
+
         self.__offsets = self.__connectivity = None
         return None
 
diff --git a/pyvista/core/errors.py b/pyvista/core/errors.py
index 5d94fb5b..4e1e475a 100644
--- a/pyvista/core/errors.py
+++ b/pyvista/core/errors.py
@@ -129,6 +129,21 @@ class AmbiguousDataError(ValueError):
         super().__init__(message)
 
 
+class CellSizeError(ValueError):
+    """Exception when a cell array size is invalid.
+
+    Parameters
+    ----------
+    message : str
+        Error message.
+
+    """
+
+    def __init__(self, message="Cell array size is invalid."):
+        """Call the base class constructor with the custom message."""
+        super().__init__(message)
+
+
 class PyVistaPipelineError(RuntimeError):
     """Exception when a VTK pipeline runs into an issue.
 


Test patch:
diff --git a/tests/core/test_grid.py b/tests/core/test_grid.py
index 52c7be34..c75e2167 100644
--- a/tests/core/test_grid.py
+++ b/tests/core/test_grid.py
@@ -8,7 +8,12 @@ import vtk
 
 import pyvista as pv
 from pyvista import CellType, examples
-from pyvista.core.errors import AmbiguousDataError, MissingDataError, PyVistaDeprecationWarning
+from pyvista.core.errors import (
+    AmbiguousDataError,
+    CellSizeError,
+    MissingDataError,
+    PyVistaDeprecationWarning,
+)
 
 test_path = os.path.dirname(os.path.abspath(__file__))
 
@@ -93,7 +98,14 @@ def test_init_bad_input():
         pv.UnstructuredGrid(np.array(1))
 
     with pytest.raises(TypeError, match="must be a numeric type"):
-        pv.UnstructuredGrid(np.array(1), np.array(1), 'woa')
+        pv.UnstructuredGrid(np.array([2, 0, 1]), np.array(1), 'woa')
+
+    with pytest.raises(CellSizeError, match="Cell array size is invalid"):
+        rnd_generator = np.random.default_rng()
+        points = rnd_generator.random((4, 3))
+        celltypes = [pv.CellType.TETRA]
+        cells = np.array([5, 0, 1, 2, 3])
+        pv.UnstructuredGrid(cells, celltypes, points)
 
     with pytest.raises(TypeError, match="requires the following arrays"):
         pv.UnstructuredGrid(*range(5))
diff --git a/tests/core/test_polydata.py b/tests/core/test_polydata.py
index e3b75d5d..8151862f 100644
--- a/tests/core/test_polydata.py
+++ b/tests/core/test_polydata.py
@@ -8,7 +8,7 @@ import pytest
 
 import pyvista as pv
 from pyvista import examples
-from pyvista.core.errors import NotAllTrianglesError, PyVistaFutureWarning
+from pyvista.core.errors import CellSizeError, NotAllTrianglesError, PyVistaFutureWarning
 
 radius = 0.5
 
@@ -174,6 +174,25 @@ def test_invalid_file():
         pv.PolyData(filename)
 
 
+@pytest.mark.parametrize(
+    "arr,value",
+    [
+        ("faces", [3, 1, 2, 3, 3, 0, 1]),
+        ("strips", np.array([5, 4, 3, 2, 0])),
+        ("lines", [4, 0, 1, 2, 2, 3, 4]),
+        ("verts", [1, 0, 1]),
+        ("faces", [[3, 0, 1], [3, 2, 1], [4, 0, 1]]),
+        ("faces", [[2, 0, 1], [2, 2, 1], [1, 0, 1]]),
+    ],
+)
+def test_invalid_connectivity_arrays(arr, value):
+    generator = np.random.default_rng(seed=None)
+    points = generator.random((10, 3))
+    mesh = pv.PolyData(points)
+    with pytest.raises(CellSizeError, match="Cell array size is invalid"):
+        setattr(mesh, arr, value)
+
+
 def test_lines_on_init():
     lines = [2, 0, 1, 3, 2, 3, 4]
     points = np.random.default_rng().random((5, 3))