1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use std::path::PathBuf;
use clap::{Parser, Subcommand};
pub mod config;
pub mod filter;
pub mod paths;
mod commands {
pub mod analyze;
pub mod diff;
pub mod fix;
pub mod list;
pub mod report;
pub mod watch;
}
mod output {
pub mod terminal;
}
#[derive(Parser)]
#[command(
name = "padlock",
about = "Struct memory layout analyzer for C, C++, Rust, and Go",
version
)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Analyze one or more files or directories for struct layout issues
Analyze {
/// Paths to analyze: source files (.c .cpp .rs .go), binaries, or directories
#[arg(num_args = 1.., value_name = "PATH")]
paths: Vec<PathBuf>,
/// Output as JSON
#[arg(long)]
json: bool,
/// Output as SARIF (for CI / GitHub Code Scanning annotations)
#[arg(long)]
sarif: bool,
#[command(flatten)]
filter: filter::FilterArgs,
},
/// List all structs found in one or more files with basic stats
List {
/// Paths to analyze: source files, binaries, or directories
#[arg(num_args = 1.., value_name = "PATH")]
paths: Vec<PathBuf>,
#[command(flatten)]
filter: filter::FilterArgs,
},
/// Show a diff of original vs optimal field ordering
Diff {
/// Source files or directories to diff (binaries not supported)
#[arg(num_args = 1.., value_name = "PATH")]
paths: Vec<PathBuf>,
/// Include only structs whose names match this regex pattern
#[arg(long, short = 'F', value_name = "PATTERN")]
filter: Option<String>,
},
/// Apply automatic field reordering to source files in-place
Fix {
/// Source files or directories to fix (binaries not supported)
#[arg(num_args = 1.., value_name = "PATH")]
paths: Vec<PathBuf>,
/// Show the diff without writing any files
#[arg(long)]
dry_run: bool,
/// Include only structs whose names match this regex pattern
#[arg(long, short = 'F', value_name = "PATTERN")]
filter: Option<String>,
},
/// Generate a full report (alias for analyze)
Report {
/// Paths to analyze: source files, binaries, or directories
#[arg(num_args = 1.., value_name = "PATH")]
paths: Vec<PathBuf>,
#[arg(long)]
json: bool,
#[command(flatten)]
filter: filter::FilterArgs,
},
/// Watch a file or directory and re-analyse on every change
Watch {
/// Path to watch (source file, binary, or directory)
path: PathBuf,
/// Output results as JSON on each refresh
#[arg(long)]
json: bool,
},
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
match cli.command {
Commands::Analyze {
paths,
json,
sarif,
filter,
} => commands::analyze::run(&paths, json, sarif, &filter),
Commands::List { paths, filter } => commands::list::run(&paths, &filter),
Commands::Diff { paths, filter } => commands::diff::run(&paths, filter.as_deref()),
Commands::Fix {
paths,
dry_run,
filter,
} => commands::fix::run(&paths, dry_run, filter.as_deref()),
Commands::Report {
paths,
json,
filter,
} => commands::analyze::run(&paths, json, false, &filter),
Commands::Watch { path, json } => commands::watch::run(&path, json),
}
}