Fixed potential UB

This commit is contained in:
Lam Wei Lun
2026-09-10 12:47:43 +08:00
parent a0ba8e12e5
commit b4beae4be3
2 changed files with 49 additions and 34 deletions
+39 -34
View File
@@ -128,11 +128,11 @@ std::unique_ptr<AppAction> make_action(const std::string& plugin_key, const std:
// ---- built-in command actions (the speed dial "commands" section) ------
constexpr const char* kCommandPrefix = "orca_command";
constexpr const char* kOrcaSourceKey = "orca";
constexpr const char* kOrcaSourceName = "OrcaSlicer";
constexpr const char* kSettingPrefix = "orca_setting";
constexpr const char* kPlateGotoPrefix = "orca_plate_goto";
constexpr const char* kCommandPrefix = "orca_command";
constexpr const char* kOrcaSourceKey = "orca";
constexpr const char* kOrcaSourceName = "OrcaSlicer";
constexpr const char* kSettingPrefix = "orca_setting";
constexpr const char* kPlateGotoPrefix = "orca_plate_goto";
constexpr const char* kRecentProjectPrefix = "orca_recent_project";
// Display context for a setting action's eyebrow, e.g. the "Process" in "Process : Quality : Layers".
@@ -142,10 +142,10 @@ std::string setting_type_context(Preset::Type type)
switch (type) {
case Preset::TYPE_FILAMENT:
case Preset::TYPE_SLA_MATERIAL: return _u8L("Filament");
case Preset::TYPE_PRINTER: return _u8L("Printer");
case Preset::TYPE_PRINTER: return _u8L("Printer");
case Preset::TYPE_PRINT:
case Preset::TYPE_SLA_PRINT:
default: return _u8L("Process");
default: return _u8L("Process");
}
}
@@ -155,15 +155,19 @@ std::string setting_type_context(Preset::Type type)
// the generic registry run() bumps stats so a jump shows up in "recents" like any other action.
struct SettingAction : AppAction
{
std::string opt_key;
std::string opt_key;
Preset::Type type;
std::wstring category; // localized category, forwarded to jump_to_option
std::wstring category; // localized category, forwarded to jump_to_option
static std::string id_for(const std::string& opt_key, Preset::Type type)
{ return std::string(kSettingPrefix) + ":" + opt_key + ":" + std::to_string(int(type)); }
SettingAction(std::string opt_key_in, Preset::Type type_in, std::string title, std::string group,
std::wstring category_in, std::string source_name)
SettingAction(std::string opt_key_in,
Preset::Type type_in,
std::string title,
std::string group,
std::wstring category_in,
std::string source_name)
: AppAction(AppActionId{id_for(opt_key_in, type_in)}, std::move(title), kOrcaSourceKey, std::move(source_name))
, opt_key(std::move(opt_key_in))
, type(type_in)
@@ -182,15 +186,13 @@ struct SettingAction : AppAction
}
};
// A built-in command action. Thin value: identity + presentation come from the NativeCommands
// catalog, and run() routes back to it - the catalog is the single source of truth for its
// behaviour. source_key is the constant "orca" so a renamed title never re-keys the action
// (matches the plugin source-key contract).
// behaviour. The id is keyed by the stable catalog key (NOT the display title), so a rename or a
// UI-language switch never re-keys the action; the title is display-only.
struct CommandAction : AppAction
{
static std::unique_ptr<CommandAction> make(const NativeCommand& c)
{ return std::unique_ptr<CommandAction>(new CommandAction(c)); }
static std::unique_ptr<CommandAction> make(const NativeCommand& c) { return std::unique_ptr<CommandAction>(new CommandAction(c)); }
std::string command_key;
@@ -198,7 +200,8 @@ struct CommandAction : AppAction
private:
explicit CommandAction(const NativeCommand& c)
: AppAction(kCommandPrefix, c.title, kOrcaSourceKey, kOrcaSourceName), command_key(c.key)
: AppAction(AppActionId{AppAction::compose_id(kCommandPrefix, c.key, kOrcaSourceKey)}, c.title, kOrcaSourceKey, kOrcaSourceName),
command_key(c.key)
{
this->kind = AppActionKind::Command;
this->group = c.group;
@@ -214,12 +217,10 @@ struct PlateAction : AppAction
{
int plate_index;
static std::string id_for(int index)
{ return AppAction::compose_id(kPlateGotoPrefix, std::to_string(index), kOrcaSourceKey); }
static std::string id_for(int index) { return AppAction::compose_id(kPlateGotoPrefix, std::to_string(index), kOrcaSourceKey); }
PlateAction(int index, std::string title, std::string source_name)
: AppAction(AppActionId{id_for(index)}, std::move(title), kOrcaSourceKey, std::move(source_name))
, plate_index(index)
: AppAction(AppActionId{id_for(index)}, std::move(title), kOrcaSourceKey, std::move(source_name)), plate_index(index)
{
this->kind = AppActionKind::Command;
this->group = _u8L("Plate");
@@ -239,12 +240,10 @@ struct RecentProjectAction : AppAction
{
std::string file_path;
static std::string id_for(const std::string& path)
{ return AppAction::compose_id(kRecentProjectPrefix, path, kOrcaSourceKey); }
static std::string id_for(const std::string& path) { return AppAction::compose_id(kRecentProjectPrefix, path, kOrcaSourceKey); }
RecentProjectAction(std::string path, std::string title, std::string source)
: AppAction(AppActionId{id_for(path)}, std::move(title), kOrcaSourceKey, std::move(source))
, file_path(std::move(path))
: AppAction(AppActionId{id_for(path)}, std::move(title), kOrcaSourceKey, std::move(source)), file_path(std::move(path))
{
this->kind = AppActionKind::Command;
this->group = _u8L("Recent Projects");
@@ -538,15 +537,17 @@ void ActionRegistry::materialize_setting_actions()
// title = the option leaf name (last label segment); group stays empty so the source path
// (above) is the single display/search breadcrumb rather than being duplicated.
auto action = std::make_unique<SettingAction>(opt.opt_key(), opt.type, boost::nowide::narrow(label_w),
std::string(), opt.category_local, boost::nowide::narrow(path));
auto action = std::make_unique<SettingAction>(opt.opt_key(), opt.type, boost::nowide::narrow(label_w), std::string(),
opt.category_local, boost::nowide::narrow(path));
action->favourite = std::find(favs.begin(), favs.end(), id) != favs.end();
action->favourite = std::find(favs.begin(), favs.end(), id) != favs.end();
if (auto it = stats.find(id); it != stats.end() && it->is_object()) {
action->count = it->value("count", 0);
action->last = it->value("last", 0LL);
}
m_actions.insert_or_assign(action->id(), std::shared_ptr<AppAction>(std::move(action)));
auto const action_id = action->id();
auto const app_action = std::shared_ptr<AppAction>(std::move(action));
m_actions.insert_or_assign(action_id, app_action);
}
// Drop SettingActions whose option no longer exists in the current configs (e.g. the printer
@@ -600,13 +601,15 @@ void ActionRegistry::materialize_plate_actions()
if (!name.empty())
title += " (" + name + ")";
auto action = std::make_unique<PlateAction>(int(i), title, kOrcaSourceName);
auto action = std::make_unique<PlateAction>(int(i), title, kOrcaSourceName);
action->favourite = std::find(favs.begin(), favs.end(), id) != favs.end();
if (auto it = stats.find(id); it != stats.end() && it->is_object()) {
action->count = it->value("count", 0);
action->last = it->value("last", 0LL);
}
m_actions.insert_or_assign(action->id(), std::shared_ptr<AppAction>(std::move(action)));
auto const action_id = action->id();
auto const app_action = std::shared_ptr<AppAction>(std::move(action));
m_actions.insert_or_assign(action_id, app_action);
}
// Drop plate actions whose index no longer exists (a plate was deleted / moved to the front).
@@ -649,13 +652,15 @@ void ActionRegistry::materialize_recent_project_actions()
if (title.empty())
title = path;
auto action = std::make_unique<RecentProjectAction>(path, std::move(title), path);
action->favourite = std::find(favs.begin(), favs.end(), id) != favs.end();
auto action = std::make_unique<RecentProjectAction>(path, std::move(title), path);
action->favourite = std::find(favs.begin(), favs.end(), id) != favs.end();
if (auto it = stats.find(id); it != stats.end() && it->is_object()) {
action->count = it->value("count", 0);
action->last = it->value("last", 0LL);
}
m_actions.insert_or_assign(action->id(), std::shared_ptr<AppAction>(std::move(action)));
auto const action_id = action->id();
auto const app_action = std::shared_ptr<AppAction>(std::move(action));
m_actions.insert_or_assign(action_id, app_action);
}
// Drop recent-project actions whose file no longer exists / was removed from the recents list.
+10
View File
@@ -71,3 +71,13 @@ TEST_CASE("Recent-project actions are keyed by path, not title", "[speeddial][ac
CHECK(AppAction::compose_id("orca_recent_project", "C:/Data/cube.3mf", "orca") ==
"orca_recent_project:C:/Data/cube.3mf:orca");
}
// A built-in command is keyed by its stable catalog key (not the localized display title), so a
// rename or a UI-language switch never re-keys the action and its persisted favourite/stats survive.
TEST_CASE("Command actions are keyed by catalog key, not display title", "[speeddial][actions]")
{
CHECK(AppAction::compose_id("orca_command", "save_project", "orca") == "orca_command:save_project:orca");
// The second field is the stable key, so distinct commands never collide.
CHECK(AppAction::compose_id("orca_command", "save_project", "orca") !=
AppAction::compose_id("orca_command", "load_project", "orca"));
}