1use std::{cell::RefCell, collections::BTreeMap, panic};
2
3use ps_parser::PowerShellSession;
4
5thread_local! {
6 static LAST: RefCell<String> = RefCell::new(String::new());
7}
8
9const TARGETED: &[&str] = &[
10 "9223372036854775807 + 1",
11 "-9223372036854775807 - 10",
12 "9223372036854775807 * 2",
13 "$a = 9223372036854775807; $a++",
14 "[long]::MinValue / -1",
15 "(-9223372036854775807 - 1) / -1",
16 "(-9223372036854775807 - 1) % -1",
17 "5 / '0.4'",
18 "5 % '0.4'",
19 "5 / 0.4",
20 "5 % 0.4",
21 "1 -shl 64",
22 "1 -shl -1",
23 "1 -shr 100",
24 "'zażółć'.Substring(3)",
25 "'zażółć'.Substring(2,2)",
26 "'zażółć'.Remove(3)",
27 "'abc'.Substring(-1)",
28 "'abc'.Substring(1,-1)",
29 "'abc'.Remove(-1)",
30 "'abc'.Substring(9223372036854775807, 1)",
31 "'zażółć'.PadLeft(10)",
32 "'zażółć'.Trim('ż')",
33 "'zażółć'.ToUpper()",
34 "'zażółć'.IndexOf('ó')",
35 "'zażółć'[3]",
36 "'abc'[-10]",
37 "@(1,2,3)[-10]",
38 "@(1,2,3)[9223372036854775807]",
39 "@()[0]",
40 "'abc'[1..-1]",
41 "'zażółć' -split 'ó'",
42 "'zażółć' -replace 'ó','o'",
43 "'a' -split ''",
44 "'{0}{1}' -f 1",
45 "'{99999999999}' -f 1",
46 "'{0' -f 1",
47 "'{' -f 1",
48 "[char]-1",
49 "[char]99999999",
50 "[int]'99999999999999999999'",
51 "[int]9223372036854775807",
52 "[byte]300",
53 "[System.Text.Encoding]::Unicode.GetString([byte[]](1,2,3))",
54 "[System.Text.Encoding]::UTF8.GetString([byte[]](255,254))",
55 "[Convert]::FromBase64String('abc')",
56 "[Convert]::FromBase64String('====')",
57 "[System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('YQ=='))",
58 "powershell -enc",
59 "powershell -enc ''",
60 "powershell -enc YQ==",
61 "powershell -e '!!!'",
62 "powershell -encodedcommand -enc",
63 "& {}",
64 "& {param($a)} ",
65 "function f {} ; f 1 2 3",
66 "$null.foo.bar()",
67 "$null[0]",
68 "$x = @{}; $x[$null]",
69 "switch ($null) {}",
70 "switch (1) { default {} default {} }",
71 "1..-1",
72 "'a'..'z'",
73 "[char]'a'..[char]'c'",
74 "\"$(\"",
75 "\"$(1\"",
76 "'''",
77 "@'\n'@",
78 "@\"\n\"@",
79 "$",
80 "${",
81 "${}",
82 "$env:",
83 "-",
84 "--",
85 "!",
86 "[",
87 "[]",
88 "[int]",
89 "[int]::",
90 "::",
91 "()",
92 "@(",
93 "@{",
94 "@{a=}",
95 "{",
96 "}",
97 "|",
98 "| x",
99 "1 |",
100 "a.b.c(",
101 ".",
102 "&",
103 "&&",
104 "`",
105 "#",
106 "<#",
107 "class A { }",
108 "class A { [int]$x; A() { } }",
109 "class",
110 "function",
111 "if",
112 "if ()",
113 "while",
114 "for (;;) { break }",
115 "foreach",
116 "try { } catch",
117 "1 -as [int]",
118 "1 -is",
119 "'a' -like",
120 "'a' -match '('",
121 "'a' -replace '(', 'b'",
122 "'a' -split '('",
123 "'a' -match '\\'",
124 "-join",
125 "-join $null",
126 "1 -bxor",
127 "-not",
128 "[System.Convert]::ToInt32('ff', 16)",
129 "[System.Convert]::ToInt32('ff', 0)",
130 "[System.Convert]::ToInt32('zz', 16)",
131 "[System.Convert]::ToString(5, 0)",
132 "[string]::new('a', -1)",
133 "[string]::Join()",
134 "'abc'.Split()",
135 "'abc'.Insert(10, 'x')",
136 "'abc'.Replace('', 'x')",
137 "'abc'.LastIndexOf('c', 100)",
138 "'abc'.IndexOf('c', 100)",
139 "'{0:N70000}' -f 1.5",
140 "'{0:F70000}' -f 1.5",
141 "'{0:N}' -f 1.5",
142 "'{0:}' -f 1.5",
143 "'{0,70000}' -f 1",
144 "'{0,-2147483648}' -f 1",
145 "'{0,2147483647}' -f 1",
146 "'{0:#,0}' -f 1",
147 "'{ż}' -f 1",
148 "'{0,ż}' -f 1",
149 "'{0:ż}' -f 1",
150 "'a'.Split('a', -9223372036854775807 - 1)",
151 "'a'.Split('a', -5)",
152 "@(1,2) * 0",
153 "'a' * 0",
154 "class B : A { }",
155 "class A { [int]$x; A() { } }; [A]::new()",
156 "'{0:' + '0' * 70000 + '}' -f 1",
157];
158
159fn depth_matrix() {
161 let patterns: &[(&str, &str, &str)] = &[
162 ("paren", "(", ")"),
163 ("block", "& {", "}"),
164 ("sub", "$(", ")"),
165 ("dq", "\"$(", ")\""),
166 ("idx", "$a[", "]"),
167 ("if", "if(1){", "}"),
168 ("ifelse", "if(0){1}else{", "}"),
169 ("arr", "@(", ")"),
170 ("hash", "@{a=", "}"),
171 ("neg", "-(", ")"),
172 ("method", "'a'.Replace((", "),'b')"),
173 ("plus", "+(", ")"),
174 ("not", "!(", ")"),
175 ("cast", "[int](", ")"),
176 ("sb", "{", "}"),
177 ("cmd", "echo (", ")"),
178 ];
179 let only = std::env::args().nth(2);
180 let depths: Vec<usize> = match std::env::args().nth(3) {
181 Some(d) => d.split(',').map(|d| d.parse().unwrap()).collect(),
182 None => vec![5, 10, 20, 40, 60, 80, 100, 300, 1500, 5000],
183 };
184 for n in depths {
185 println!("--- depth {n}");
186 let mut cases: Vec<(String, String)> = patterns
187 .iter()
188 .filter(|(name, _, _)| only.as_deref().is_none_or(|o| o == "all" || o == *name))
189 .map(|(name, o, c)| (name.to_string(), format!("$a=1;{}1{}", o.repeat(n), c.repeat(n))))
190 .collect();
191 cases.push((
192 "func".into(),
193 format!("function f($n) {{ if ($n -gt 0) {{ f ($n - 1) }} else {{ 'done' }} }}; f {n}"),
194 ));
195 for (name, script) in cases {
196 let (tx, rx) = std::sync::mpsc::channel();
197 std::thread::Builder::new()
198 .stack_size(1 << 20)
199 .spawn(move || {
200 let t = std::time::Instant::now();
201 let r = PowerShellSession::new().parse_script(&script).map(|r| r.result());
202 let _ = tx.send(format!("{:?} [{} ms]", r, t.elapsed().as_millis()));
203 })
204 .unwrap();
205 let res = rx
206 .recv_timeout(std::time::Duration::from_secs(20))
207 .unwrap_or_else(|_| "TIMEOUT".into());
208 let short: String = res.chars().take(70).collect();
209 println!("{name:>7}: {short}");
210 }
211 }
212 std::process::exit(0);
213}
214
215fn run(script: &str) -> Option<String> {
216 LAST.with(|l| l.borrow_mut().clear());
217 let res = panic::catch_unwind(|| {
218 let mut ps = PowerShellSession::new();
219 let _ = ps.parse_script(script);
220 let _ = PowerShellSession::new().parse_command(script);
221 });
222 let last = LAST.with(|l| l.borrow().clone());
224 (res.is_err() || !last.is_empty()).then_some(last)
225}
226
227fn main() {
228 panic::set_hook(Box::new(|info| {
229 let loc = info
230 .location()
231 .map(|l| format!("{}:{}", l.file(), l.line()))
232 .unwrap_or_default();
233 let msg = info
234 .payload()
235 .downcast_ref::<&str>()
236 .map(|s| s.to_string())
237 .or_else(|| info.payload().downcast_ref::<String>().cloned())
238 .unwrap_or_default();
239 LAST.with(|l| *l.borrow_mut() = format!("{loc} | {msg}"));
240 }));
241
242 if std::env::args().nth(1).as_deref() == Some("--depth") {
243 depth_matrix();
244 return;
245 }
246
247 if let Some(script) = std::env::args().nth(1) {
248 let (tx, rx) = std::sync::mpsc::channel();
249 std::thread::spawn(move || {
250 let t = std::time::Instant::now();
251 let r = PowerShellSession::new().parse_script(&script).map(|r| r.result());
252 let _ = tx.send(format!("[{} ms] {:?}", t.elapsed().as_millis(), r));
253 });
254 let res = rx
255 .recv_timeout(std::time::Duration::from_secs(15))
256 .unwrap_or_else(|_| "TIMEOUT".into());
257 println!("{}", res.chars().take(200).collect::<String>());
258 std::process::exit(0);
259 }
260
261 let mut found: BTreeMap<String, (usize, String)> = BTreeMap::new();
263 let mut record = |input: &str, panic: String| {
264 let key = panic.split(" | ").next().unwrap_or_default().to_string();
265 let e = found.entry(key).or_insert((0, input.to_string()));
266 e.0 += 1;
267 if input.len() < e.1.len() {
268 e.1 = input.to_string();
269 }
270 let _ = panic;
271 };
272
273 let mut runs = 0;
274 for s in TARGETED {
275 runs += 1;
276 if let Some(p) = run(s) {
277 println!("TARGETED PANIC: {s:?}\n {p}");
278 record(s, p);
279 }
280 }
281
282 let corpus = [
283 "test_scripts/comprehensive/input.ps1",
284 "test_scripts/parser_focused/input.ps1",
285 "test_scripts/stress/input.ps1",
286 "test_scripts/enc_oneliner.ps1",
287 ];
288 for path in corpus {
289 let Ok(text) = std::fs::read_to_string(path) else {
291 continue;
292 };
293 let lines: Vec<&str> = text.lines().collect();
294
295 for line in &lines {
297 let idxs: Vec<usize> = line.char_indices().map(|(i, _)| i).chain([line.len()]).collect();
298 for &i in &idxs {
299 runs += 1;
300 let s = &line[..i];
301 if let Some(p) = run(s) {
302 record(s, p);
303 }
304 }
305 }
306
307 for n in 0..=lines.len() {
309 runs += 1;
310 let s = lines[..n].join("\n");
311 if let Some(p) = run(&s) {
312 record(&s, p);
313 }
314 }
315 }
316
317 println!("\n=== {runs} runs, {} distinct panic locations ===", found.len());
318 for (loc, (count, input)) in &found {
319 let short: String = input.chars().take(160).collect();
320 println!("{loc} (x{count})\n shortest: {short:?}");
321 }
322}