diff --git a/src/UIGridPyMethods.cpp b/src/UIGridPyMethods.cpp index 2c248fa..0562287 100644 --- a/src/UIGridPyMethods.cpp +++ b/src/UIGridPyMethods.cpp @@ -14,6 +14,7 @@ #include "EntityBehavior.h" #include "PyTrigger.h" #include "UIBase.h" +#include "PyFOV.h" // ========================================================================= // Cell access: py_at, subscript, mpmethods @@ -97,10 +98,10 @@ PyObject* UIGrid::py_compute_fov(PyUIGridObject* self, PyObject* args, PyObject* PyObject* pos_obj = NULL; int radius = 0; int light_walls = 1; - int algorithm = FOV_BASIC; + PyObject* algorithm_obj = NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|ipi", const_cast(kwlist), - &pos_obj, &radius, &light_walls, &algorithm)) { + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|ipO", const_cast(kwlist), + &pos_obj, &radius, &light_walls, &algorithm_obj)) { return NULL; } @@ -109,7 +110,17 @@ PyObject* UIGrid::py_compute_fov(PyUIGridObject* self, PyObject* args, PyObject* return NULL; } - self->data->computeFOV(x, y, radius, light_walls, (TCOD_fov_algorithm_t)algorithm); + // #310: validate algorithm via PyFOV::from_arg so out-of-range ints become a + // ValueError at the Python boundary instead of a UBSan-flagged invalid enum + // load deep in GridData::computeFOV. + TCOD_fov_algorithm_t algorithm = FOV_BASIC; + if (algorithm_obj != NULL) { + if (!PyFOV::from_arg(algorithm_obj, &algorithm)) { + return NULL; + } + } + + self->data->computeFOV(x, y, radius, light_walls, algorithm); Py_RETURN_NONE; } diff --git a/tests/regression/issue_310_compute_fov_enum_test.py b/tests/regression/issue_310_compute_fov_enum_test.py new file mode 100644 index 0000000..42596bb --- /dev/null +++ b/tests/regression/issue_310_compute_fov_enum_test.py @@ -0,0 +1,67 @@ +"""Regression test for issue #310. + +Grid.compute_fov's algorithm argument was passed directly as an int into +GridData::computeFOV, where it was cast to TCOD_fov_algorithm_t. Values +outside the enum range caused UBSan to report an invalid enum load. Fuzz +target fuzz_fov surfaced this with algorithm=-49. + +The fix routes the argument through PyFOV::from_arg, which accepts FOV +enum members, ints in [0, NB_FOV_ALGORITHMS), or None (defaults to BASIC), +and raises ValueError for out-of-range ints. +""" + +import mcrfpy +import sys + + +def main(): + grid = mcrfpy.Grid(grid_size=(10, 10)) + + # Valid paths: enum, int, None, omitted + grid.compute_fov((5, 5), radius=3, algorithm=mcrfpy.FOV.BASIC) + grid.compute_fov((5, 5), radius=3, algorithm=0) # FOV_BASIC + grid.compute_fov((5, 5), radius=3, algorithm=None) # default + grid.compute_fov((5, 5), radius=3) # default, no arg + + # Out-of-range negative int (this was the UBSan trigger) + try: + grid.compute_fov((5, 5), radius=3, algorithm=-49) + except ValueError: + pass + else: + print("FAIL: algorithm=-49 should raise ValueError") + sys.exit(1) + + # Out-of-range positive int (above NB_FOV_ALGORITHMS sentinel) + try: + grid.compute_fov((5, 5), radius=3, algorithm=9999) + except ValueError: + pass + else: + print("FAIL: algorithm=9999 should raise ValueError") + sys.exit(1) + + # Wrong type still rejected + try: + grid.compute_fov((5, 5), radius=3, algorithm="basic") + except (TypeError, ValueError): + pass + else: + print("FAIL: algorithm='basic' should raise TypeError") + sys.exit(1) + + # Boundary int -1 should fail (negative) + try: + grid.compute_fov((5, 5), radius=3, algorithm=-1) + except ValueError: + pass + else: + print("FAIL: algorithm=-1 should raise ValueError") + sys.exit(1) + + print("PASS") + sys.exit(0) + + +if __name__ == "__main__": + main()