Skip to content

Fix Module copy assignment#1734

Open
robekl wants to merge 1 commit intojgromes:masterfrom
robekl:fix/module-copy-assignment
Open

Fix Module copy assignment#1734
robekl wants to merge 1 commit intojgromes:masterfrom
robekl:fix/module-copy-assignment

Conversation

@robekl
Copy link
Copy Markdown

@robekl robekl commented Mar 23, 2026

Summary

  • fix Module::operator= so it copies from the source object into the destination
  • stop assignment from mutating the source object's spiConfig
  • copy RF switch state and interrupt timing state as part of assignment
  • add a regression test covering Module copy assignment behavior

Why this is a bug

The original Module::operator= copied this->spiConfig into mod.spiConfig instead of copying from mod into this.

That had two bad effects:

  • the destination object did not receive the source SPI configuration
  • the source object was silently mutated by assignment

The original operator also did not copy the RF switch state, so assignment produced an incomplete Module copy even aside from the reversed spiConfig copy.

History

Module copy constructor and assignment were introduced in commit b8de06288 ("Added copy ctor and assignment operator", July 5, 2020). The bad copy direction in operator= dates back to that introduction.

I did not find a later mitigation elsewhere in-tree. In-tree usage of Module assignment appears to be rare, which likely kept this bug latent, but the operator is public API and its current behavior is incorrect.

Validation

I ran the full existing unit suite in extras/test/unit with Homebrew GCC/Boost/fmt paths configured.

Against original code

I copied only the new regression test into a clean worktree at the original HEAD and reran the full suite. The new test failed, showing that:

  • the assigned object did not receive RF switch state
  • the source object's spiConfig was overwritten by assignment

Against this fix

I ran the full unit suite after the fix.

Result:

  • *** No errors detected

Test coverage

Adds Module_copy_assignment_copies_spi_config_without_mutating_source in:

  • extras/test/unit/tests/TestModule.cpp

The test verifies:

  • destination receives spiConfig
  • destination receives RF switch state
  • source object is not mutated by assignment

@jgromes jgromes added the ai slop Word salad vomited out by ChatGPT or a similar langauge model - interaction is discouraged label Mar 23, 2026
Copy link
Copy Markdown
Owner

@jgromes jgromes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I agree that this is indeed a bug, honest question, why do you feel the need to generate a novel-length wall of text for it? Seriously, the only problem was that the assignment operator implementation was missing some of the members. This happened because as these new member variables were added to the Module class, they were not added to the operator.

The provided test does not guard this in the future BTW - if we add some member variable to the Module class and not add it to the test, it will happily pass as it is not checking the newly added variable. So why is the test here?


BOOST_FIXTURE_TEST_SUITE(suite_Module, ModuleFixture)

BOOST_FIXTURE_TEST_CASE(Module_copy_assignment_copies_spi_config_without_mutating_source, ModuleFixture)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No human in the history of the world would ever name a test case like this. Why is Module_copy_assignment insufficient?

{
BOOST_TEST_MESSAGE("--- Test Module copy assignment copies SPI config correctly ---");

Module other(hal, EMULATED_RADIO_NSS_PIN, EMULATED_RADIO_IRQ_PIN, EMULATED_RADIO_RST_PIN, EMULATED_RADIO_GPIO_PIN);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you really want to ensure the source is unchanged, you have to set different pins and check them.

this->irqPin = mod.irqPin;
this->rstPin = mod.rstPin;
this->gpioPin = mod.gpioPin;
memcpy(this->rfSwitchPins, mod.rfSwitchPins, sizeof(this->rfSwitchPins));
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is size of this copy not checked against Module::RFSWITCH_MAX_PINS?

mod->spiConfig.timeout = 4321;
mod->rfSwitchPins[0] = 1001;
mod->rfSwitchPins[1] = 1002;
mod->rfSwitchTable = reinterpret_cast<const Module::RfSwitchMode_t*>(0x1234);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you reusing the module fixture to do this? It seems prone to breaking in the future if the approach to its setup/teardown changes. Especially since mod->rfSwitchTable is pointing a an arbitrary memory address.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai slop Word salad vomited out by ChatGPT or a similar langauge model - interaction is discouraged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants