Fix possible repeat key infinite recursion #25926
Open
+55
−16
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
It was possible when performing the following sequence on a fresh boot for the keyboard to hang due to infinite recursion:
Only occurred in fresh boot state (i.e., repeat key needed to be the first key pressed). Have implemented a fix and added a unit test which fails before the implemented fix, and passes afterwards.
Description
The possibility of infinite recursion was mentioned in a comment at the beginning of
repeat_key_invoke("Since this function callsprocess_record(), it may recursively call itself"). This was apparently meant to be guarded against by checking ifprocessing_repeat_countis non-zero, and returning immediately if it is. Later on in the function, there are these lines:So, the intention is that guards are added around the
process_record()call by settingprocessing_repeat_countto something non-zero so that ifrepeat_key_invokeis called again, it would return without further recursion.However,
registered_repeat_countis set (to something non-zero) only in the block above theprocess_record()call ifevent->pressedis non-zero.Note also that there is a guard at the beginning of the function that returns immediately if
last_record.keycodeis zero. Effectively, this means ifset_last_record()has not been called to set the last keycode,repeat_key_invoke()would return immediately.Now, imagine this sequence:
repeat_key_invoke()would return immediately due to the check againstlast_record.keycode- we don't yet have a last keycode.set_last_record()being called, which then setslast_record.keycode, so that check will pass in the next call torepeat_key_invoke().repeat_key_invoke()would be called again, and pass the initial guard because we have a last keycode now. Because it is a release event, the block that setsregistered_repeat_countto something non-zero is never executed. So, the line before theprocess_record()call never setsprocessing_repeat_countto something non-zero, and there is no guard to prevent infinite recursion.Proposed fix is to guard against this by wrapping the whole block containing the call to
process_record()in a check to ensure thatregistered_repeat_countis non-zero, which ensures that the guard against infinite recursion will always be activated as intended.Coming up with a unit test to reproduce the issue prior to the fix proved a little tricky. Because reproduction depends on static data in
repeat_key.cbeing in a "fresh boot" state (i.e., zeroed), and because that static data is not intrinsically reset between tests, it is necessary to explicitly reset the data, or else the test will only reproduce the issue if it is the first one executed. We don't want to depend on test order, so a new function which can be used to reset the data is necessary. Some of the data needing to be reset was defined as static local withinrepeat_key_invoke, so in order to allow it to be externally reset (by the new test), the definitions needed to be moved out to static global scope. Further, to clearly disambiguate them from static local variables with the same names inalt_repeat_key_invoke(), the new static global variables were renamed, appending_repeat_keyto the end (note this affects the final names of the variables referred to in some of the narrative above, but read that from the context of the state prior to any of the changes introduced here).Disclaimer: I am certainly not an expert on Google Test, but could not identify any more elegant way to force a reset to a fresh boot state at the start of a test without adding the explicit reset function. I am happy to be enlightened if there is a better way.
At first glance, it may seem that
alt_repeat_key_invoke()could be susceptible to the same issue, but static analysis and testing does not so far confirm this. Since it appears to be "ain't broke", choosing not to fix.Types of Changes
Issues Fixed or Closed by This PR
N/A
Checklist