use std::path::PathBuf;
use clap::{Args, Parser, ValueEnum};
use crate::Confidence;
#[derive(Debug, Parser)]
#[command(
name = "rllvm-query",
about = "Query captured bitcode at source level",
after_help = "With --catalog and no query, reads one query per line from stdin, \
written as on the command line, and answers them all from one load \
of the catalog.",
version
)]
pub struct QueryArgs {
#[arg(long = "llvm-version")]
pub llvm_version: bool,
#[arg(long)]
pub catalog: Option<PathBuf>,
#[command(flatten)]
pub modifiers: QueryModifiers,
#[arg(long, global = true, conflicts_with = "full")]
pub json: bool,
#[arg(long, global = true)]
pub full: bool,
#[arg(long, global = true, value_name = "PATH")]
pub overlay: Option<PathBuf>,
#[command(subcommand)]
pub command: Option<QueryCommand>,
}
#[derive(Args, Clone, Copy, Debug, Default)]
pub struct QueryModifiers {
#[arg(long, global = true)]
pub heuristics: bool,
#[arg(long, global = true)]
pub include_overlay: bool,
#[arg(long = "annotations", global = true)]
pub include_annotations: bool,
#[arg(long, global = true, value_enum, value_name = "LEVEL")]
pub min_confidence: Option<Confidence>,
}
impl QueryModifiers {
pub fn with(self, line: QueryModifiers) -> QueryModifiers {
QueryModifiers {
heuristics: self.heuristics || line.heuristics,
include_overlay: self.include_overlay || line.include_overlay,
include_annotations: self.include_annotations || line.include_annotations,
min_confidence: line.min_confidence.or(self.min_confidence),
}
}
}
#[derive(Clone, Copy, Debug, ValueEnum)]
pub enum ClosureDirection {
In,
Out,
}
#[derive(clap::Subcommand, Debug)]
#[command(rename_all = "kebab-case")]
pub enum QueryCommand {
Defs {
name: String,
},
At {
file: String,
line: u32,
},
Callers {
name: String,
},
Callees {
name: String,
},
Uses {
name: String,
},
Reach {
from: String,
to: String,
},
Slice {
from: String,
to: String,
#[arg(long, value_name = "OUT.bc")]
emit_module: Option<PathBuf>,
},
Closure {
name: String,
#[arg(value_enum)]
direction: ClosureDirection,
},
Externals,
FfiExports,
IndirectTargets {
at: String,
},
ResolutionCandidates,
Guards {
#[arg(long, value_name = "FILE:LINE")]
at: Option<String>,
#[arg(long, value_name = "N")]
operand: Option<usize>,
#[arg(long = "return")]
ret: bool,
#[arg(long)]
function: Option<String>,
#[arg(long)]
local: Option<String>,
#[arg(long)]
from: Option<String>,
#[arg(long, value_name = "OUT.bc")]
emit_module: Option<PathBuf>,
},
Mcp,
Completions {
#[arg(value_enum)]
shell: clap_complete::Shell,
},
Cache {
#[command(subcommand)]
action: Option<CacheAction>,
},
Overlay {
#[command(subcommand)]
action: OverlayAction,
},
}
#[derive(clap::Subcommand, Debug)]
pub enum OverlayAction {
Record,
List,
Compact,
}
#[derive(clap::Subcommand, Debug)]
pub enum CacheAction {
Clear {
#[arg(long)]
stale: bool,
},
}
#[cfg(test)]
mod tests {
use clap::{Parser, error::ErrorKind};
use super::QueryArgs;
#[test]
fn json_and_full_cannot_be_combined() {
let error =
QueryArgs::try_parse_from(["rllvm-query", "--json", "--full", "callers", "add"])
.unwrap_err();
assert_eq!(error.kind(), ErrorKind::ArgumentConflict);
}
#[test]
fn the_heuristics_flag_may_follow_its_subcommand() {
let args = QueryArgs::try_parse_from([
"rllvm-query",
"indirect-targets",
"main.c:2",
"--heuristics",
])
.unwrap();
assert!(args.modifiers.heuristics);
}
}