fix(windows): change option_stack to deque

when vectors are resized the memory is realocated so all the option
pointers become invalid. Using a deque avoids this it has all the same
methods so it is straight replacement.

fixes: #15961
This commit is contained in:
rc-swag 2026-07-23 21:52:32 +10:00
parent 01e101babc
commit a07fff8be3
4 changed files with 71 additions and 38 deletions

View file

@ -130,7 +130,9 @@ namespace km {
}
// For this mock processor we only support queuing PERSIST_OPT action items.
if (action_item->type == KM_CORE_IT_PERSIST_OPT && action_item->option) {
state->actions().push_persist(update_option(static_cast<km_core_option_scope>(action_item->option->scope), action_item->option->key, action_item->option->value));
state->actions().push_persist(update_option(static_cast<km_core_option_scope>(action_item->option->scope),
action_item->option->key,
action_item->option->value));
return true;
}
else {
@ -175,6 +177,18 @@ namespace km {
u"F2 pressed test save."));
break;
}
case KM_CORE_VKEY_F3:
{
state->actions().push_persist(
update_option(KM_CORE_OPT_KEYBOARD,
u"__test_point_3",
u"F3 pressed test save 1."));
state->actions().push_persist(
update_option(KM_CORE_OPT_KEYBOARD,
u"__test_point_4",
u"F3 pressed test save 2."));
break;
}
case KM_CORE_VKEY_F4:
state->context().push_marker(KM_CORE_VKEY_QUOTE);

View file

@ -40,17 +40,16 @@ actions::actions(actions const &other)
: std::vector<action>(other)
, _option_items_stack(other._option_items_stack)
{
// Update all option pointers to point to the new stack
// Update all option pointers to point to the new stack.
size_t opt_index = 0;
for (auto &item : *this) {
if (item.type == KM_CORE_IT_PERSIST_OPT && item.option) {
// Find the corresponding option in the new stack
// The pointers in the original point to positions in other._option_items_stack
// We need to find the equivalent position in our _option_items_stack
auto original_ptr = item.option;
auto original_base = reinterpret_cast<km_core_option_item const *>(other._option_items_stack.data());
auto offset = original_ptr - original_base;
if (offset >= 0 && static_cast<size_t>(offset) < _option_items_stack.size()) {
item.option = &_option_items_stack[offset];
if (item.type == KM_CORE_IT_PERSIST_OPT) {
if (opt_index < _option_items_stack.size()) {
item.option = &_option_items_stack[opt_index++];
} else {
// no matching item in the stack; clear pointer.
item.option = nullptr;
}
}
}

View file

@ -10,6 +10,7 @@
#include <cassert>
#include <vector>
#include <deque>
#include "keyman_core.h"
@ -27,7 +28,7 @@ using action = km_core_action_item;
class actions : public std::vector<action>
{
std::vector<option> _option_items_stack;
std::deque<option> _option_items_stack;
template<km_core_action_type V>
void _push_vkey(km_core_virtual_key);

View file

@ -251,11 +251,23 @@ constexpr km_core_option_item const expected_persist_opt = {
KM_CORE_OPT_KEYBOARD
};
//constexpr km_core_option_item const clone_persist_opt = {
// u"__test_clone",
// u"Not in original",
// KM_CORE_OPT_KEYBOARD
//};
constexpr km_core_option_item const clone_persist_opt = {
u"__test_clone",
u"Not in original",
KM_CORE_OPT_KEYBOARD
};
constexpr km_core_option_item const test_point_3_opt = {
u"__test_point_3",
u"F3 pressed test save 1.",
KM_CORE_OPT_KEYBOARD
};
constexpr km_core_option_item const test_point_4_opt = {
u"__test_point_4",
u"F3 pressed test save 2.",
KM_CORE_OPT_KEYBOARD
};
extern "C"
{
@ -275,8 +287,8 @@ int main(int argc, char * argv[])
km_core_keyboard * test_kb = nullptr;
km_core_state * test_state = nullptr,
* test_clone = nullptr;
// * test_clone2 = nullptr;
* test_clone = nullptr,
* test_clone_2 = nullptr;
test_kb = (km_core_keyboard *)new km::core::mock_processor(km::core::path("dummy.mock"));
// Simple sanity tests.
@ -401,34 +413,41 @@ int main(int argc, char * argv[])
clone_state_deleted_text
));
// Need to test the option pointer is pointing to the correct the correct _option_items_stack.
// This could not be tested like this as the way commit() works adding KM_CORE_IT_END to the
// end of the actions list. This means when calling km_core_state_queue_action_items
// I get an assert in push_persist "empty() || back().type != KM_CORE_IT_END" it is a protection
// against adding a action to an already commited list.
// To test this change I need to do mocking closer to the integration of that actions list.
// Add two actions before cloning the state again
try_status(km_core_process_event(test_state, KM_CORE_VKEY_F3, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
try_status(km_core_state_clone(test_state, &test_clone_2));
// Now put an option in the test_clone_2 state only
km_core_action_item action_clone = {KM_CORE_IT_PERSIST_OPT, {0,}, };
action_clone.option = &clone_persist_opt;
if (test_clone_2->actions().back().type == KM_CORE_IT_END) {
test_clone_2->actions().pop_back();
}
km_core_state_queue_action_items(test_clone_2, &action_clone);
test_clone_2->actions().commit();
// Test debug dump
auto doc3 = get_json_doc(*test_state), doc4 = get_json_doc(*test_clone_2);
std::cout << "doc3:" << std::endl;
std::cout << doc3 << std::endl;
std::cout << "doc4:" << std::endl;
std::cout << doc4 << std::endl;
if (doc3 == doc4) return __LINE__;
// km_core_action_item actions_test[] = {
// { KM_CORE_IT_PERSIST_OPT, {0,}, },
// { KM_CORE_IT_END, {0,}, }
// };
km_core_action_item action_tp3 = {KM_CORE_IT_PERSIST_OPT, {0,}, };
action_tp3.option = &test_point_3_opt;
// actions_test[0].option = &clone_persist_opt;
km_core_action_item action_tp4 = {KM_CORE_IT_PERSIST_OPT, {0,}, };
action_tp4.option = &test_point_4_opt;
//try_status(km_core_state_create(test_kb, test_env_opts, &test_clone2));
//km_core_state_queue_action_items(test_clone2, actions_test);
//test_assert(action_items(test_clone2, actions_test));
//test_assert(action_items(test_clone2, {action_clone, {KM_CORE_IT_END}}));
test_assert(action_items(test_state, {action_tp3, action_tp4, {KM_CORE_IT_END}}));
// Check that test_clone_2 has the same persisted options plus the extra queued option.
test_assert(action_items(test_clone_2, {action_tp3, action_tp4, action_clone, {KM_CORE_IT_END}}));
// Destroy them
km_core_state_dispose(test_state);
km_core_state_dispose(test_clone);
//km_core_state_dispose(test_clone2);
km_core_state_dispose(test_clone_2);
km_core_keyboard_dispose(test_kb);
return 0;