From abd1c642ab03f38633487ccddae416f217e3a68b Mon Sep 17 00:00:00 2001 From: Azra Bano Date: Fri, 4 Sep 2026 11:33:25 -0400 Subject: [PATCH] mavgen_cpp11: fix IndexError when an enum entry is a prefix of the enum name The C++11 generator strips the enum name from each entry to build the scoped `enum class` names. enum_remove_prefix() pops one leading component of the entry for every matching component of the enum name, but never checks that something is left. For an entry whose name is a prefix of the enum name, e.g. the list of components runs empty and the next comparison raises: File "generator/mavgen_cpp11.py", line 304, in enum_remove_prefix if pl[i] == sl[0]: IndexError: list index out of range Stop stripping once a single component is left, so the entry keeps its last component (SOME_ENUM -> ENUM). This only changes behaviour for inputs that previously crashed: any entry that used to strip down to exactly one component is unaffected, and regenerating C++11 headers for message_definitions/v1.0/all.xml produces byte-identical output. Add tests for enum_remove_prefix() and for end-to-end C++11 generation from an XML file containing such an enum. Fixes #887 Signed-off-by: Azra Bano Co-Authored-By: Claude Fable 5.1 --- generator/mavgen_cpp11.py | 5 ++- tests/test_mavgen_cpp11.py | 64 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 tests/test_mavgen_cpp11.py diff --git a/generator/mavgen_cpp11.py b/generator/mavgen_cpp11.py index 86faa5572..b235606cc 100644 --- a/generator/mavgen_cpp11.py +++ b/generator/mavgen_cpp11.py @@ -301,7 +301,10 @@ def enum_remove_prefix(prefix, s): sl = s.split('_') for i in range(len(pl)): - if pl[i] == sl[0]: + # keep at least one component, so that an entry whose name is a + # prefix of the enum name (e.g. SOME_ENUM in SOME_ENUM_NAME) does + # not end up with an empty name + if len(sl) > 1 and pl[i] == sl[0]: sl = sl[1:] else: break diff --git a/tests/test_mavgen_cpp11.py b/tests/test_mavgen_cpp11.py new file mode 100644 index 000000000..1e0081bd4 --- /dev/null +++ b/tests/test_mavgen_cpp11.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +""" +Tests for the C++11 generator. +""" + +from pathlib import Path +import shutil +import sys + +try: + from pymavlink.generator import mavgen + from pymavlink.generator import mavgen_cpp11 +except ModuleNotFoundError: + sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + from generator import mavgen + from generator import mavgen_cpp11 + + +def test_enum_remove_prefix(): + assert mavgen_cpp11.enum_remove_prefix("MAV_CMD", "MAV_CMD_NAV_WAYPOINT") == "NAV_WAYPOINT" + assert mavgen_cpp11.enum_remove_prefix("MAV_FRAME", "MAV_FRAME_GLOBAL") == "GLOBAL" + # a remainder starting with a digit gets the last prefix component back + assert mavgen_cpp11.enum_remove_prefix("MAV_SYS_STATUS_SENSOR", "MAV_SYS_STATUS_SENSOR_3D_GYRO") == "SENSOR_3D_GYRO" + # an entry whose name is a prefix of the enum name must keep its last component + assert mavgen_cpp11.enum_remove_prefix("SOME_ENUM_NAME", "SOME_ENUM") == "ENUM" + assert mavgen_cpp11.enum_remove_prefix("SOME_ENUM_NAME", "SOME_ENUM_NAME") == "NAME" + assert mavgen_cpp11.enum_remove_prefix("SOME_ENUM_NAME", "SOME") == "SOME" + + +def test_cpp11_generator_enum_entry_is_prefix_of_enum_name(tmp_path): + xml_filepath = tmp_path / "test.xml" + xml_filepath.write_text(""" + + 3 + + + + + + + + + +""") + output_dir = Path(__file__).resolve().parents[1] / ".tmp" / "cpp11-generator" + shutil.rmtree(output_dir, ignore_errors=True) + output_dir.mkdir(parents=True, exist_ok=True) + + ok = mavgen.mavgen( + mavgen.Opts( + output=str(output_dir), + language="C++11", + wire_protocol="2.0", + validate=False, + ), + [str(xml_filepath)], + ) + + assert ok is True + + header = (output_dir / "test" / "test.hpp").read_text(encoding="utf-8") + assert "enum class SOME_ENUM_NAME" in header + assert " ENUM=0," in header + assert " VALUE=1," in header