-
Notifications
You must be signed in to change notification settings - Fork 6
Description
Description
DynamicCustomCommand::metadata() in src/cortex-engine/src/commands/custom.rs calls Box::leak(meta) every time it is invoked, permanently leaking a heap-allocated CommandMeta struct on each call. Since metadata() is called frequently (e.g., on every command registry lookup, help display, tab completion), this causes unbounded memory growth proportional to the number of calls.
Affected File
src/cortex-engine/src/commands/custom.rs, lines 56-78
fn metadata(&self) -> &CommandMeta {
// We need to leak the metadata to get a static reference
// This is safe because custom commands are loaded once and live for the program duration
let meta = Box::new(CommandMeta {
name: self.command.name.clone(),
// ...
});
Box::leak(meta) // <-- leaks a new allocation on EVERY call
}Root Cause
The CommandHandler::metadata() trait method returns &CommandMeta (a reference tied to &self's lifetime). The comment says "custom commands are loaded once" but Box::leak is called on a newly allocated Box on every invocation — not once. Each call allocates a new CommandMeta and leaks it permanently.
The built-in commands (e.g., HelpCommand, ClearCommand) correctly use std::sync::OnceLock to initialize the metadata exactly once:
fn metadata(&self) -> &CommandMeta {
static META: std::sync::OnceLock<CommandMeta> = std::sync::OnceLock::new();
META.get_or_init(|| CommandMeta::new(...))
}Steps to Reproduce
- Load any custom command via
CommandRegistry::load_custom_commands - Call
metadata()on the resultingDynamicCustomCommandhandler repeatedly (e.g., viaregistry.list()or tab completion) - Observe heap growth — each call leaks ~200+ bytes
Expected Behavior
metadata() should return a reference to a stable, once-initialized CommandMeta without allocating on each call.
Actual Behavior
Each call to metadata() allocates and leaks a new CommandMeta on the heap.
Fix
Store the CommandMeta in the struct itself and return a reference to it:
pub struct DynamicCustomCommand {
command: crate::custom_command::CustomCommand,
meta: CommandMeta,
}
impl DynamicCustomCommand {
pub fn new(command: crate::custom_command::CustomCommand) -> Self {
let meta = CommandMeta { /* build once */ };
Self { command, meta }
}
}
impl CommandHandler for DynamicCustomCommand {
fn metadata(&self) -> &CommandMeta {
&self.meta
}
}