use crate::diagnostics::print_warning;
use crate::repl::*;
const ALIAS_RECURSION_LIMIT: usize = 16;
const BREAKPOINT_ACTION_RECURSION_LIMIT: usize = 4;
mod analyze;
mod breakpoints;
mod cpu;
mod diagnostics;
mod exceptions;
mod exec;
mod frames;
mod heap;
mod inspect;
mod memory;
mod meta;
mod mm;
mod physical;
mod pnp;
mod process;
mod sched;
mod security;
mod symbols;
mod target_control;
mod types;
mod usermode;
mod verifier;
impl ReplState<'_> {
pub fn dispatch_line(&mut self, line: &str) -> Result<Flow> {
let flow = self.dispatch_line_inner(line, 0);
self.flush_notices();
flow
}
pub fn flush_notices(&mut self) {
for notice in self.ctx.take_notices() {
print_warning(notice);
}
}
pub fn line_moves_target(&self, line: &str) -> bool {
self.line_has_command(line, 0, &|spec| spec.run != RunEffect::None)
}
pub fn line_needs_halt(&self, line: &str) -> bool {
self.line_has_command(line, 0, &|spec| spec.run_state == Some(RunState::Halted))
}
fn line_has_command(
&self,
line: &str,
depth: usize,
matches: &dyn Fn(&CommandSpec) -> bool,
) -> bool {
let Ok(commands) = split_command_list(line) else {
return false;
};
commands.into_iter().any(|command| {
let Ok(Some(parsed)) = parse_command(command) else {
return false;
};
if let Some(spec) = command_registry().get(parsed.name) {
return matches(spec);
}
let Ok(invocation) = parsed.invocation(CommandStyle::StructuredArgs) else {
return false;
};
match self.aliases.expand(invocation.name, &invocation.argv) {
Ok(Some(expanded)) if depth < ALIAS_RECURSION_LIMIT => {
self.line_has_command(&expanded, depth + 1, matches)
}
_ => false,
}
})
}
pub fn dispatch_breakpoint_action(&mut self, line: &str) -> Result<bool> {
if self.event_command_depth >= BREAKPOINT_ACTION_RECURSION_LIMIT {
error!("breakpoint action recursion limit reached");
return Ok(false);
}
let commands = match split_command_list(line) {
Ok(commands) => commands,
Err(error) => {
report_command_parse_error(line, error);
return Ok(false);
}
};
let continue_after = commands
.last()
.is_some_and(|command| command.trim().eq_ignore_ascii_case("gc"));
let command_count = commands.len().saturating_sub(usize::from(continue_after));
self.event_command_depth += 1;
let outer = std::mem::replace(&mut self.context, DispatchContext::BreakpointAction);
let result = (|| {
for command in commands.into_iter().take(command_count) {
let flow = match self.dispatch_one(command, 0) {
Ok(flow) => flow,
Err(error) => {
error!("breakpoint action failed: {}", error);
return Ok(false);
}
};
match flow {
Flow::Quit | Flow::Denied => return Ok(false),
Flow::Continue => {}
}
self.caches.refresh_expression_context(&self.ctx.target);
}
Ok(continue_after)
})();
self.context = outer;
self.event_command_depth -= 1;
result
}
pub fn dispatch_exception_command(&mut self, line: &str) -> Result<()> {
if self.event_command_depth >= EXCEPTION_COMMAND_RECURSION_LIMIT {
error!("exception command recursion limit reached");
return Ok(());
}
let commands = match split_command_list(line) {
Ok(commands) => commands,
Err(error) => {
report_command_parse_error(line, error);
return Ok(());
}
};
self.event_command_depth += 1;
let outer = std::mem::replace(&mut self.context, DispatchContext::ExceptionCommand);
let result = (|| {
for command in commands {
let flow = match self.dispatch_one(command, 0) {
Ok(flow) => flow,
Err(error) => {
error!("exception command failed: {}", error);
return Ok(());
}
};
match flow {
Flow::Denied => return Ok(()),
Flow::Quit => {
error!("quit is ignored inside an exception command");
return Ok(());
}
Flow::Continue => {}
}
self.caches.refresh_expression_context(&self.ctx.target);
}
Ok(())
})();
self.context = outer;
self.event_command_depth -= 1;
result
}
fn run_control_denial(&self, spec: &CommandSpec) -> Option<String> {
let name = spec.names[0];
match self.context {
DispatchContext::Interactive => None,
DispatchContext::BreakpointAction if spec.run != RunEffect::None => Some(format!(
"run-control command '{name}' must not appear inside a breakpoint action; \
use trailing 'gc' to continue"
)),
DispatchContext::ExceptionCommand
if spec.run != RunEffect::None || spec.run_state == Some(RunState::Running) =>
{
Some(format!(
"run-control command '{name}' must not appear inside an exception command; \
use the policy's -f break, -f gh, or -f gn"
))
}
DispatchContext::Remote(RemoteClient::Dap) if spec.run == RunEffect::Run => {
Some(format!(
"'{name}' would block this session until the next stop; use the client's \
continue or step controls instead"
))
}
DispatchContext::Remote(RemoteClient::Mcp) if spec.flow == Flow::Quit => Some(format!(
"'{name}' ends the interactive REPL; use the close tool to release the session"
)),
DispatchContext::Remote(RemoteClient::Dap) if spec.flow == Flow::Quit => Some(format!(
"'{name}' ends the interactive REPL; disconnect from the client to end the \
debug session"
)),
_ => None,
}
}
fn dispatch_line_inner(&mut self, line: &str, depth: usize) -> Result<Flow> {
let commands = match split_command_list(line) {
Ok(commands) => commands,
Err(err) => {
report_command_parse_error(line, err);
return Ok(Flow::Continue);
}
};
for command in commands {
match self.dispatch_one(command, depth)? {
Flow::Quit => return Ok(Flow::Quit),
Flow::Denied => return Ok(Flow::Denied),
Flow::Continue => {}
}
self.caches.refresh_expression_context(&self.ctx.target);
}
Ok(Flow::Continue)
}
fn dispatch_one(&mut self, line: &str, depth: usize) -> Result<Flow> {
if line.trim_start().starts_with('~') {
if let Some(spec) = command_registry().get("~") {
if let Some(reason) = self.run_control_denial(spec) {
error!("{reason}");
return Ok(Flow::Denied);
}
if !check_run_state(self, spec) {
return Ok(Flow::Continue);
}
}
return self.cmd_tilde(line.trim());
}
let parsed = match parse_command(line) {
Ok(Some(parsed)) => parsed,
Ok(None) => return Ok(Flow::Continue),
Err(err) => {
report_command_parse_error(line, err);
return Ok(Flow::Continue);
}
};
if let Some(spec) = command_registry().get(parsed.name) {
if let Some(reason) = self.run_control_denial(spec) {
error!("{reason}");
return Ok(Flow::Denied);
}
if !check_run_state(self, spec) {
return Ok(Flow::Continue);
}
match spec.handler {
CommandHandler::NoArgs(handler) => {
if !parsed.raw_tail.trim().is_empty() {
outln!("{}\n", command_help(parsed.name));
return Ok(Flow::Continue);
}
handler(self)?;
}
CommandHandler::Args(handler) => {
let invocation = match parsed.invocation(spec.style) {
Ok(invocation) => invocation,
Err(err) => {
report_command_parse_error(line, err);
return Ok(Flow::Continue);
}
};
handler(self, invocation)?;
}
}
return Ok(spec.flow);
}
let invocation = match parsed.invocation(CommandStyle::StructuredArgs) {
Ok(invocation) => invocation,
Err(err) => {
report_command_parse_error(line, err);
return Ok(Flow::Continue);
}
};
match self.aliases.expand(invocation.name, &invocation.argv) {
Ok(Some(expanded)) => {
if depth >= ALIAS_RECURSION_LIMIT {
error!("alias expansion limit reached");
return Ok(Flow::Continue);
}
return self.dispatch_line_inner(&expanded, depth + 1);
}
Ok(None) => {}
Err(err) => {
error!("{}", err);
return Ok(Flow::Continue);
}
}
self.cmd_user(invocation)?;
Ok(Flow::Continue)
}
}