pygfx / pygfx

Powerful and versatile visualization for Python.
https://pygfx.com
BSD 2-Clause "Simplified" License
402 stars 36 forks source link

Crashing pygfx with zero length arrays #812

Closed hmaarrfk closed 4 weeks ago

hmaarrfk commented 1 month ago

Show how you can crash rust with a 0-length array

I'm really sorry for opening so many issues but this one seems intellectually interesting,

It seems that a zero length position array (which can occur easily if you try to procedurally generate points and hit an edge case) can cause a rust panic.

I modified the example code to show how it can occur in a reproducible way.

Backtrace included.

Not sure if there is a good way to avoid these errors at a pygfx/wgpu level.

$ RUST_BACKTRACE=1 python examples/introductory/points_basic.py
Detected skylake derivative running on mesa i915. Clears to srgb textures will use manual shader clears.
thread '<unnamed>' panicked at src/conv.rs:1193:30:
invalid size
stack backtrace:
   0: rust_begin_unwind
             at /rustc/25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04/library/std/src/panicking.rs:647:5
   1: core::panicking::panic_fmt
             at /rustc/25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04/library/core/src/panicking.rs:72:14
   2: <core::iter::adapters::map::Map<I,F> as core::iter::traits::iterator::Iterator>::fold
   3: <alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter
   4: wgpuDeviceCreateBindGroup
   5: ffi_call_unix64
   6: ffi_call_int
   7: cdata_call
   8: _PyObject_Call
             at /usr/local/src/conda/python-3.12.4/Objects/call.c:367:19
   9: PyCFunction_Call
             at /usr/local/src/conda/python-3.12.4/Objects/call.c:387:12
  10: _PyEval_EvalFrameDefault
             at /home/conda/feedstock_root/build_artifacts/python-split_1718618246744/work/build-static/Python/bytecodes.c:3262:26
  11: _PyEval_EvalFrame
             at /usr/local/src/conda/python-3.12.4/Include/internal/pycore_ceval.h:89:16
  12: _PyEval_Vector
             at /usr/local/src/conda/python-3.12.4/Python/ceval.c:1683:12
  13: _PyFunction_Vectorcall
             at /usr/local/src/conda/python-3.12.4/Objects/call.c:419:16
  14: _PyObject_VectorcallTstate
             at /usr/local/src/conda/python-3.12.4/Include/internal/pycore_call.h:92:11
  15: method_vectorcall
             at /usr/local/src/conda/python-3.12.4/Objects/classobject.c:91:18
  16: <unknown>
  17: _ZN7QWidget5eventEP6QEvent
  18: _ZN19QApplicationPrivate13notify_helperEP7QObjectP6QEvent
  19: _ZN16QCoreApplication15notifyInternal2EP7QObjectP6QEvent
  20: _ZN14QWidgetPrivate14sendPaintEventERK7QRegion
  21: _ZN14QWidgetPrivate10drawWidgetEP12QPaintDeviceRK7QRegionRK6QPoint6QFlagsINS_14DrawWidgetFlagEEP8QPainterP21QWidgetRepaintManager
  22: _ZN14QWidgetPrivate13paintOnScreenERK7QRegion
  23: _ZN13QWidgetWindow5eventEP6QEvent
  24: _ZN19QApplicationPrivate13notify_helperEP7QObjectP6QEvent
  25: _ZN16QCoreApplication15notifyInternal2EP7QObjectP6QEvent
  26: _ZN22QGuiApplicationPrivate18processExposeEventEPN29QWindowSystemInterfacePrivate11ExposeEventE
  27: _ZN22QWindowSystemInterface22sendWindowSystemEventsE6QFlagsIN10QEventLoop17ProcessEventsFlagEE
  28: _ZL17xcbSourceDispatchP8_GSourcePFiPvES1_
  29: g_main_dispatch
  30: g_main_context_iterate_unlocked.constprop.0
  31: g_main_context_iteration
  32: _ZN20QEventDispatcherGlib13processEventsE6QFlagsIN10QEventLoop17ProcessEventsFlagEE
  33: _ZN10QEventLoop4execE6QFlagsINS_17ProcessEventsFlagEE
  34: _ZN16QCoreApplication4execEv
  35: <unknown>
  36: cfunction_vectorcall_NOARGS
             at /usr/local/src/conda/python-3.12.4/Objects/methodobject.c:481:24
  37: _PyObject_VectorcallTstate
             at /usr/local/src/conda/python-3.12.4/Include/internal/pycore_call.h:92:11
  38: PyObject_Vectorcall
             at /usr/local/src/conda/python-3.12.4/Objects/call.c:325:12
  39: _PyEval_EvalFrameDefault
             at /home/conda/feedstock_root/build_artifacts/python-split_1718618246744/work/build-static/Python/bytecodes.c:2714:19
  40: PyEval_EvalCode
             at /usr/local/src/conda/python-3.12.4/Python/ceval.c:578:21
  41: run_eval_code_obj
             at /usr/local/src/conda/python-3.12.4/Python/pythonrun.c:1722
  42: run_mod
             at /usr/local/src/conda/python-3.12.4/Python/pythonrun.c:1743
  43: pyrun_file
             at /usr/local/src/conda/python-3.12.4/Python/pythonrun.c:1643
  44: _PyRun_SimpleFileObject
             at /usr/local/src/conda/python-3.12.4/Python/pythonrun.c:433
  45: _PyRun_AnyFileObject
             at /usr/local/src/conda/python-3.12.4/Python/pythonrun.c:78
  46: pymain_run_file_obj
             at /usr/local/src/conda/python-3.12.4/Modules/main.c:360
  47: pymain_run_file
             at /usr/local/src/conda/python-3.12.4/Modules/main.c:379
  48: pymain_run_python
             at /usr/local/src/conda/python-3.12.4/Modules/main.c:629
  49: Py_RunMain
             at /usr/local/src/conda/python-3.12.4/Modules/main.c:709
  50: Py_BytesMain
             at /usr/local/src/conda/python-3.12.4/Modules/main.c:763:12
  51: __libc_start_call_main
             at ./csu/../sysdeps/nptl/libc_start_call_main.h:58:16
  52: __libc_start_main_impl
             at ./csu/../csu/libc-start.c:360:3
  53: <unknown>
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
fatal runtime error: failed to initiate panic, error 5
Aborted (core dumped)

See https://github.com/hmaarrfk/pygfx/pull/7/files for diff

diff --git a/examples/introductory/points_basic.py b/examples/introductory/points_basic.py
index 96eb8de21..06f87a157 100644
--- a/examples/introductory/points_basic.py
+++ b/examples/introductory/points_basic.py
@@ -21,8 +21,11 @@
 scene = gfx.Scene()

 positions = np.random.normal(0, 0.5, (100, 3)).astype(np.float32)
-sizes = np.random.rand(100).astype(np.float32) * 50
-colors = np.random.rand(100, 4).astype(np.float32)
+
+# But slice it beyond its length to test a (0, 3) array
+positions = positions[100:]
+sizes = np.random.rand(positions.shape[0]).astype(np.float32) * 50
+colors = np.random.rand(positions.shape[0], 4).astype(np.float32)
 geometry = gfx.Geometry(positions=positions, sizes=sizes, colors=colors)

 material = gfx.PointsMaterial(color_mode="vertex", size_mode="vertex")
almarklein commented 1 month ago

I can reproduce this. In a regular pygfx example I also get a crash. I also quickly changed a benchmark for buffers to use zero elements and then I get "Invalid usage flags BufferUsages(0x0)". Seems like this case is not properly covered in wgpu-core yet.

I can include a check in the pygfx Buffer, and maybe also in wgpu-py.

hmaarrfk commented 1 month ago

It could be something we report further upstream.

My rust knowledge is 0

almarklein commented 1 month ago

In #795 I added a check for buffer/texture size. Would be good to (eventually) add checks upstream, but this fixes it for us and there's plenty more important things to do 🤷

hmaarrfk commented 4 weeks ago

Thanks for addressing this. this is pretty huge!