1use std::collections::HashSet;
4use std::sync::OnceLock;
5
6use crate::ast::{
7 Block, DerefKind, Expr, ExprKind, MatchArrayElem, Program, Sigil, Statement, StmtKind,
8 StringPart, SubSigParam,
9};
10use crate::error::{ErrorKind, PerlError, PerlResult};
11
12static BUILTINS: OnceLock<HashSet<&'static str>> = OnceLock::new();
13
14fn builtins() -> &'static HashSet<&'static str> {
15 BUILTINS.get_or_init(|| {
16 include_str!("lsp_completion_words.txt")
17 .lines()
18 .map(str::trim)
19 .filter(|l| !l.is_empty() && !l.starts_with('#'))
20 .collect()
21 })
22}
23
24#[derive(Default)]
25struct Scope {
26 scalars: HashSet<String>,
27 arrays: HashSet<String>,
28 hashes: HashSet<String>,
29 subs: HashSet<String>,
30}
31
32impl Scope {
33 fn declare_scalar(&mut self, name: &str) {
34 self.scalars.insert(name.to_string());
35 }
36 fn declare_array(&mut self, name: &str) {
37 self.arrays.insert(name.to_string());
38 }
39 fn declare_hash(&mut self, name: &str) {
40 self.hashes.insert(name.to_string());
41 }
42 fn declare_sub(&mut self, name: &str) {
43 self.subs.insert(name.to_string());
44 }
45}
46
47pub struct StaticAnalyzer {
48 scopes: Vec<Scope>,
49 errors: Vec<PerlError>,
50 file: String,
51 current_package: String,
52}
53
54impl StaticAnalyzer {
55 pub fn new(file: &str) -> Self {
56 let mut global = Scope::default();
57 for name in ["_", "a", "b", "ARGV", "ENV", "SIG", "INC"] {
58 global.declare_array(name);
59 }
60 for name in ["ENV", "SIG", "INC"] {
61 global.declare_hash(name);
62 }
63 for name in [
64 "_", "a", "b", "!", "$", "@", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "&",
65 "`", "'", "+", ".", "/", "\\", "|", "%", "=", "-", "~", "^", "*", "?", "\"",
66 ] {
67 global.declare_scalar(name);
68 }
69 Self {
70 scopes: vec![global],
71 errors: Vec::new(),
72 file: file.to_string(),
73 current_package: "main".to_string(),
74 }
75 }
76
77 fn push_scope(&mut self) {
78 self.scopes.push(Scope::default());
79 }
80
81 fn pop_scope(&mut self) {
82 if self.scopes.len() > 1 {
83 self.scopes.pop();
84 }
85 }
86
87 fn declare_scalar(&mut self, name: &str) {
88 if let Some(scope) = self.scopes.last_mut() {
89 scope.declare_scalar(name);
90 }
91 }
92
93 fn declare_array(&mut self, name: &str) {
94 if let Some(scope) = self.scopes.last_mut() {
95 scope.declare_array(name);
96 }
97 }
98
99 fn declare_hash(&mut self, name: &str) {
100 if let Some(scope) = self.scopes.last_mut() {
101 scope.declare_hash(name);
102 }
103 }
104
105 fn declare_sub(&mut self, name: &str) {
106 if let Some(scope) = self.scopes.first_mut() {
107 scope.declare_sub(name);
108 }
109 }
110
111 fn is_scalar_defined(&self, name: &str) -> bool {
112 if is_special_var(name) {
113 return true;
114 }
115 self.scopes.iter().rev().any(|s| s.scalars.contains(name))
116 }
117
118 fn is_array_defined(&self, name: &str) -> bool {
119 if name == "_" || name == "ARGV" {
120 return true;
121 }
122 self.scopes.iter().rev().any(|s| s.arrays.contains(name))
123 }
124
125 fn is_hash_defined(&self, name: &str) -> bool {
126 if matches!(name, "ENV" | "SIG" | "INC") {
127 return true;
128 }
129 self.scopes.iter().rev().any(|s| s.hashes.contains(name))
130 }
131
132 fn is_sub_defined(&self, name: &str) -> bool {
133 if name.starts_with("static::") {
135 return true;
136 }
137 let base = name.rsplit("::").next().unwrap_or(name);
138 if builtins().contains(base) {
139 return true;
140 }
141 self.scopes
142 .iter()
143 .rev()
144 .any(|s| s.subs.contains(name) || s.subs.contains(base))
145 }
146
147 fn error(&mut self, kind: ErrorKind, msg: String, line: usize) {
148 self.errors
149 .push(PerlError::new(kind, msg, line, &self.file));
150 }
151
152 pub fn analyze(mut self, program: &Program) -> PerlResult<()> {
153 for stmt in &program.statements {
154 self.collect_declarations_stmt(stmt);
155 }
156 for stmt in &program.statements {
157 self.analyze_stmt(stmt);
158 }
159 if let Some(e) = self.errors.into_iter().next() {
160 Err(e)
161 } else {
162 Ok(())
163 }
164 }
165
166 fn collect_declarations_stmt(&mut self, stmt: &Statement) {
167 match &stmt.kind {
168 StmtKind::Package { name } => {
169 self.current_package = name.clone();
170 }
171 StmtKind::SubDecl { name, .. } => {
172 let fqn = if name.contains("::") {
173 name.clone()
174 } else {
175 format!("{}::{}", self.current_package, name)
176 };
177 self.declare_sub(name);
178 self.declare_sub(&fqn);
179 }
180 StmtKind::Use { module, .. } => {
181 self.declare_sub(module);
182 }
183 StmtKind::Block(b)
184 | StmtKind::StmtGroup(b)
185 | StmtKind::Begin(b)
186 | StmtKind::End(b)
187 | StmtKind::UnitCheck(b)
188 | StmtKind::Check(b)
189 | StmtKind::Init(b) => {
190 for s in b {
191 self.collect_declarations_stmt(s);
192 }
193 }
194 StmtKind::If {
195 body,
196 elsifs,
197 else_block,
198 ..
199 } => {
200 for s in body {
201 self.collect_declarations_stmt(s);
202 }
203 for (_, b) in elsifs {
204 for s in b {
205 self.collect_declarations_stmt(s);
206 }
207 }
208 if let Some(b) = else_block {
209 for s in b {
210 self.collect_declarations_stmt(s);
211 }
212 }
213 }
214 StmtKind::ClassDecl { def } => {
215 self.declare_sub(&def.name);
217 for m in &def.methods {
219 if m.is_static {
220 self.declare_sub(&format!("{}::{}", def.name, m.name));
221 }
222 }
223 for sf in &def.static_fields {
224 self.declare_sub(&format!("{}::{}", def.name, sf.name));
225 }
226 }
227 StmtKind::StructDecl { def } => {
228 self.declare_sub(&def.name);
229 }
230 StmtKind::EnumDecl { def } => {
231 self.declare_sub(&def.name);
232 for v in &def.variants {
233 self.declare_sub(&format!("{}::{}", def.name, v.name));
234 }
235 }
236 _ => {}
237 }
238 }
239
240 fn analyze_stmt(&mut self, stmt: &Statement) {
241 match &stmt.kind {
242 StmtKind::Package { name } => {
243 self.current_package = name.clone();
244 }
245 StmtKind::My(decls)
246 | StmtKind::Our(decls)
247 | StmtKind::Local(decls)
248 | StmtKind::State(decls)
249 | StmtKind::MySync(decls) => {
250 for d in decls {
251 match d.sigil {
252 Sigil::Scalar => self.declare_scalar(&d.name),
253 Sigil::Array => self.declare_array(&d.name),
254 Sigil::Hash => self.declare_hash(&d.name),
255 Sigil::Typeglob => {}
256 }
257 if let Some(init) = &d.initializer {
258 self.analyze_expr(init);
259 }
260 }
261 }
262 StmtKind::Expression(e) => self.analyze_expr(e),
263 StmtKind::Return(Some(e)) => self.analyze_expr(e),
264 StmtKind::Return(None) => {}
265 StmtKind::If {
266 condition,
267 body,
268 elsifs,
269 else_block,
270 } => {
271 self.analyze_expr(condition);
272 self.push_scope();
273 self.analyze_block(body);
274 self.pop_scope();
275 for (cond, b) in elsifs {
276 self.analyze_expr(cond);
277 self.push_scope();
278 self.analyze_block(b);
279 self.pop_scope();
280 }
281 if let Some(b) = else_block {
282 self.push_scope();
283 self.analyze_block(b);
284 self.pop_scope();
285 }
286 }
287 StmtKind::Unless {
288 condition,
289 body,
290 else_block,
291 } => {
292 self.analyze_expr(condition);
293 self.push_scope();
294 self.analyze_block(body);
295 self.pop_scope();
296 if let Some(b) = else_block {
297 self.push_scope();
298 self.analyze_block(b);
299 self.pop_scope();
300 }
301 }
302 StmtKind::While {
303 condition,
304 body,
305 continue_block,
306 ..
307 }
308 | StmtKind::Until {
309 condition,
310 body,
311 continue_block,
312 ..
313 } => {
314 self.analyze_expr(condition);
315 self.push_scope();
316 self.analyze_block(body);
317 if let Some(cb) = continue_block {
318 self.analyze_block(cb);
319 }
320 self.pop_scope();
321 }
322 StmtKind::DoWhile { body, condition } => {
323 self.push_scope();
324 self.analyze_block(body);
325 self.pop_scope();
326 self.analyze_expr(condition);
327 }
328 StmtKind::For {
329 init,
330 condition,
331 step,
332 body,
333 continue_block,
334 ..
335 } => {
336 self.push_scope();
337 if let Some(i) = init {
338 self.analyze_stmt(i);
339 }
340 if let Some(c) = condition {
341 self.analyze_expr(c);
342 }
343 if let Some(s) = step {
344 self.analyze_expr(s);
345 }
346 self.analyze_block(body);
347 if let Some(cb) = continue_block {
348 self.analyze_block(cb);
349 }
350 self.pop_scope();
351 }
352 StmtKind::Foreach {
353 var,
354 list,
355 body,
356 continue_block,
357 ..
358 } => {
359 self.analyze_expr(list);
360 self.push_scope();
361 self.declare_scalar(var);
362 self.analyze_block(body);
363 if let Some(cb) = continue_block {
364 self.analyze_block(cb);
365 }
366 self.pop_scope();
367 }
368 StmtKind::SubDecl {
369 name, params, body, ..
370 } => {
371 let fqn = if name.contains("::") {
372 name.clone()
373 } else {
374 format!("{}::{}", self.current_package, name)
375 };
376 self.declare_sub(name);
377 self.declare_sub(&fqn);
378 self.push_scope();
379 for p in params {
380 self.declare_param(p);
381 }
382 self.analyze_block(body);
383 self.pop_scope();
384 }
385 StmtKind::Block(b)
386 | StmtKind::StmtGroup(b)
387 | StmtKind::Begin(b)
388 | StmtKind::End(b)
389 | StmtKind::UnitCheck(b)
390 | StmtKind::Check(b)
391 | StmtKind::Init(b)
392 | StmtKind::Continue(b) => {
393 self.push_scope();
394 self.analyze_block(b);
395 self.pop_scope();
396 }
397 StmtKind::TryCatch {
398 try_block,
399 catch_var,
400 catch_block,
401 finally_block,
402 } => {
403 self.push_scope();
404 self.analyze_block(try_block);
405 self.pop_scope();
406 self.push_scope();
407 self.declare_scalar(catch_var);
408 self.analyze_block(catch_block);
409 self.pop_scope();
410 if let Some(fb) = finally_block {
411 self.push_scope();
412 self.analyze_block(fb);
413 self.pop_scope();
414 }
415 }
416 StmtKind::EvalTimeout { body, .. } => {
417 self.push_scope();
418 self.analyze_block(body);
419 self.pop_scope();
420 }
421 StmtKind::Given { topic, body } => {
422 self.analyze_expr(topic);
423 self.push_scope();
424 self.analyze_block(body);
425 self.pop_scope();
426 }
427 StmtKind::When { cond, body } => {
428 self.analyze_expr(cond);
429 self.push_scope();
430 self.analyze_block(body);
431 self.pop_scope();
432 }
433 StmtKind::DefaultCase { body } => {
434 self.push_scope();
435 self.analyze_block(body);
436 self.pop_scope();
437 }
438 StmtKind::LocalExpr {
439 target,
440 initializer,
441 } => {
442 self.analyze_expr(target);
443 if let Some(init) = initializer {
444 self.analyze_expr(init);
445 }
446 }
447 StmtKind::Goto { target } => {
448 self.analyze_expr(target);
449 }
450 StmtKind::Tie { class, args, .. } => {
451 self.analyze_expr(class);
452 for a in args {
453 self.analyze_expr(a);
454 }
455 }
456 StmtKind::Use { imports, .. } | StmtKind::No { imports, .. } => {
457 for e in imports {
458 self.analyze_expr(e);
459 }
460 }
461 StmtKind::StructDecl { .. }
462 | StmtKind::EnumDecl { .. }
463 | StmtKind::ClassDecl { .. }
464 | StmtKind::TraitDecl { .. }
465 | StmtKind::FormatDecl { .. }
466 | StmtKind::UsePerlVersion { .. }
467 | StmtKind::UseOverload { .. }
468 | StmtKind::Last(_)
469 | StmtKind::Next(_)
470 | StmtKind::Redo(_)
471 | StmtKind::Empty => {}
472 }
473 }
474
475 fn declare_param(&mut self, param: &SubSigParam) {
476 match param {
477 SubSigParam::Scalar(name, _) => self.declare_scalar(name),
478 SubSigParam::Array(name) => self.declare_array(name),
479 SubSigParam::Hash(name) => self.declare_hash(name),
480 SubSigParam::ArrayDestruct(elems) => {
481 for e in elems {
482 match e {
483 MatchArrayElem::CaptureScalar(n) => self.declare_scalar(n),
484 MatchArrayElem::RestBind(n) => self.declare_array(n),
485 _ => {}
486 }
487 }
488 }
489 SubSigParam::HashDestruct(pairs) => {
490 for (_, name) in pairs {
491 self.declare_scalar(name);
492 }
493 }
494 }
495 }
496
497 fn analyze_block(&mut self, block: &Block) {
498 for stmt in block {
499 self.analyze_stmt(stmt);
500 }
501 }
502
503 fn analyze_expr(&mut self, expr: &Expr) {
504 match &expr.kind {
505 ExprKind::ScalarVar(name) if !self.is_scalar_defined(name) => {
506 self.error(
507 ErrorKind::UndefinedVariable,
508 format!("Global symbol \"${}\" requires explicit package name", name),
509 expr.line,
510 );
511 }
512 ExprKind::ArrayVar(name) if !self.is_array_defined(name) => {
513 self.error(
514 ErrorKind::UndefinedVariable,
515 format!("Global symbol \"@{}\" requires explicit package name", name),
516 expr.line,
517 );
518 }
519 ExprKind::HashVar(name) if !self.is_hash_defined(name) => {
520 self.error(
521 ErrorKind::UndefinedVariable,
522 format!("Global symbol \"%{}\" requires explicit package name", name),
523 expr.line,
524 );
525 }
526 ExprKind::ArrayElement { array, index } => {
527 if !self.is_array_defined(array) && !self.is_scalar_defined(array) {
528 self.error(
529 ErrorKind::UndefinedVariable,
530 format!(
531 "Global symbol \"@{}\" requires explicit package name",
532 array
533 ),
534 expr.line,
535 );
536 }
537 self.analyze_expr(index);
538 }
539 ExprKind::HashElement { hash, key } => {
540 if !self.is_hash_defined(hash) && !self.is_scalar_defined(hash) {
541 self.error(
542 ErrorKind::UndefinedVariable,
543 format!("Global symbol \"%{}\" requires explicit package name", hash),
544 expr.line,
545 );
546 }
547 self.analyze_expr(key);
548 }
549 ExprKind::ArraySlice { array, indices } => {
550 if !self.is_array_defined(array) {
551 self.error(
552 ErrorKind::UndefinedVariable,
553 format!(
554 "Global symbol \"@{}\" requires explicit package name",
555 array
556 ),
557 expr.line,
558 );
559 }
560 for i in indices {
561 self.analyze_expr(i);
562 }
563 }
564 ExprKind::HashSlice { hash, keys } => {
565 if !self.is_hash_defined(hash) {
566 self.error(
567 ErrorKind::UndefinedVariable,
568 format!("Global symbol \"%{}\" requires explicit package name", hash),
569 expr.line,
570 );
571 }
572 for k in keys {
573 self.analyze_expr(k);
574 }
575 }
576 ExprKind::FuncCall { name, args } => {
577 if !self.is_sub_defined(name) {
578 self.error(
579 ErrorKind::UndefinedSubroutine,
580 format!("Undefined subroutine &{}", name),
581 expr.line,
582 );
583 }
584 for a in args {
585 self.analyze_expr(a);
586 }
587 }
588 ExprKind::MethodCall { object, args, .. } => {
589 self.analyze_expr(object);
590 for a in args {
591 self.analyze_expr(a);
592 }
593 }
594 ExprKind::IndirectCall { target, args, .. } => {
595 self.analyze_expr(target);
596 for a in args {
597 self.analyze_expr(a);
598 }
599 }
600 ExprKind::BinOp { left, right, .. } => {
601 self.analyze_expr(left);
602 self.analyze_expr(right);
603 }
604 ExprKind::UnaryOp { expr: e, .. } => {
605 self.analyze_expr(e);
606 }
607 ExprKind::PostfixOp { expr: e, .. } => {
608 self.analyze_expr(e);
609 }
610 ExprKind::Assign { target, value } => {
611 if let ExprKind::ScalarVar(name) = &target.kind {
612 self.declare_scalar(name);
613 } else if let ExprKind::ArrayVar(name) = &target.kind {
614 self.declare_array(name);
615 } else if let ExprKind::HashVar(name) = &target.kind {
616 self.declare_hash(name);
617 } else {
618 self.analyze_expr(target);
619 }
620 self.analyze_expr(value);
621 }
622 ExprKind::CompoundAssign { target, value, .. } => {
623 self.analyze_expr(target);
624 self.analyze_expr(value);
625 }
626 ExprKind::Ternary {
627 condition,
628 then_expr,
629 else_expr,
630 } => {
631 self.analyze_expr(condition);
632 self.analyze_expr(then_expr);
633 self.analyze_expr(else_expr);
634 }
635 ExprKind::List(exprs) | ExprKind::ArrayRef(exprs) => {
636 for e in exprs {
637 self.analyze_expr(e);
638 }
639 }
640 ExprKind::HashRef(pairs) => {
641 for (k, v) in pairs {
642 self.analyze_expr(k);
643 self.analyze_expr(v);
644 }
645 }
646 ExprKind::CodeRef { params, body } => {
647 self.push_scope();
648 for p in params {
649 self.declare_param(p);
650 }
651 self.analyze_block(body);
652 self.pop_scope();
653 }
654 ExprKind::ScalarRef(e)
655 | ExprKind::Deref { expr: e, .. }
656 | ExprKind::Defined(e)
657 | ExprKind::Exists(e)
658 | ExprKind::Delete(e) => {
659 self.analyze_expr(e);
660 }
661 ExprKind::ArrowDeref { expr, index, kind } => {
662 self.analyze_expr(expr);
663 if *kind != DerefKind::Call {
664 self.analyze_expr(index);
665 }
666 }
667 ExprKind::Range { from, to, .. } => {
668 self.analyze_expr(from);
669 self.analyze_expr(to);
670 }
671 ExprKind::InterpolatedString(parts) => {
672 for part in parts {
673 match part {
674 StringPart::ScalarVar(name) => {
675 if !self.is_scalar_defined(name) {
676 self.error(
677 ErrorKind::UndefinedVariable,
678 format!(
679 "Global symbol \"${}\" requires explicit package name",
680 name
681 ),
682 expr.line,
683 );
684 }
685 }
686 StringPart::ArrayVar(name) => {
687 if !self.is_array_defined(name) {
688 self.error(
689 ErrorKind::UndefinedVariable,
690 format!(
691 "Global symbol \"@{}\" requires explicit package name",
692 name
693 ),
694 expr.line,
695 );
696 }
697 }
698 StringPart::Expr(e) => self.analyze_expr(e),
699 StringPart::Literal(_) => {}
700 }
701 }
702 }
703 ExprKind::Regex(_, _)
704 | ExprKind::Substitution { .. }
705 | ExprKind::Transliterate { .. }
706 | ExprKind::Match { .. } => {}
707 ExprKind::HashSliceDeref { container, keys } => {
708 self.analyze_expr(container);
709 for k in keys {
710 self.analyze_expr(k);
711 }
712 }
713 ExprKind::AnonymousListSlice { source, indices } => {
714 self.analyze_expr(source);
715 for i in indices {
716 self.analyze_expr(i);
717 }
718 }
719 ExprKind::SubroutineRef(name) | ExprKind::SubroutineCodeRef(name)
720 if !self.is_sub_defined(name) =>
721 {
722 self.error(
723 ErrorKind::UndefinedSubroutine,
724 format!("Undefined subroutine &{}", name),
725 expr.line,
726 );
727 }
728 ExprKind::DynamicSubCodeRef(e) => self.analyze_expr(e),
729 ExprKind::PostfixIf { expr, condition }
730 | ExprKind::PostfixUnless { expr, condition }
731 | ExprKind::PostfixWhile { expr, condition }
732 | ExprKind::PostfixUntil { expr, condition } => {
733 self.analyze_expr(expr);
734 self.analyze_expr(condition);
735 }
736 ExprKind::PostfixForeach { expr, list } => {
737 self.analyze_expr(list);
738 self.analyze_expr(expr);
739 }
740 ExprKind::Do(e) | ExprKind::Eval(e) => {
741 self.analyze_expr(e);
742 }
743 ExprKind::Caller(Some(e)) => {
744 self.analyze_expr(e);
745 }
746 ExprKind::Length(e) => {
747 self.analyze_expr(e);
748 }
749 ExprKind::Print { args, .. }
750 | ExprKind::Say { args, .. }
751 | ExprKind::Printf { args, .. } => {
752 for a in args {
753 self.analyze_expr(a);
754 }
755 }
756 ExprKind::Die(args)
757 | ExprKind::Warn(args)
758 | ExprKind::Unlink(args)
759 | ExprKind::Chmod(args)
760 | ExprKind::System(args)
761 | ExprKind::Exec(args) => {
762 for a in args {
763 self.analyze_expr(a);
764 }
765 }
766 ExprKind::Push { array, values } | ExprKind::Unshift { array, values } => {
767 self.analyze_expr(array);
768 for v in values {
769 self.analyze_expr(v);
770 }
771 }
772 ExprKind::Splice {
773 array,
774 offset,
775 length,
776 replacement,
777 } => {
778 self.analyze_expr(array);
779 if let Some(o) = offset {
780 self.analyze_expr(o);
781 }
782 if let Some(l) = length {
783 self.analyze_expr(l);
784 }
785 for r in replacement {
786 self.analyze_expr(r);
787 }
788 }
789 ExprKind::MapExpr { block, list, .. } | ExprKind::GrepExpr { block, list, .. } => {
790 self.push_scope();
791 self.analyze_block(block);
792 self.pop_scope();
793 self.analyze_expr(list);
794 }
795 ExprKind::SortExpr { list, .. } => {
796 self.analyze_expr(list);
797 }
798 ExprKind::Open { handle, mode, file } => {
799 self.analyze_expr(handle);
800 self.analyze_expr(mode);
801 if let Some(f) = file {
802 self.analyze_expr(f);
803 }
804 }
805 ExprKind::Close(e)
806 | ExprKind::Pop(e)
807 | ExprKind::Shift(e)
808 | ExprKind::Keys(e)
809 | ExprKind::Values(e)
810 | ExprKind::Each(e)
811 | ExprKind::Chdir(e)
812 | ExprKind::Require(e)
813 | ExprKind::Ref(e)
814 | ExprKind::Chomp(e)
815 | ExprKind::Chop(e)
816 | ExprKind::Lc(e)
817 | ExprKind::Uc(e)
818 | ExprKind::Lcfirst(e)
819 | ExprKind::Ucfirst(e)
820 | ExprKind::Abs(e)
821 | ExprKind::Int(e)
822 | ExprKind::Sqrt(e)
823 | ExprKind::Sin(e)
824 | ExprKind::Cos(e)
825 | ExprKind::Exp(e)
826 | ExprKind::Log(e)
827 | ExprKind::Chr(e)
828 | ExprKind::Ord(e)
829 | ExprKind::Hex(e)
830 | ExprKind::Oct(e)
831 | ExprKind::Readlink(e)
832 | ExprKind::Readdir(e)
833 | ExprKind::Closedir(e)
834 | ExprKind::Rewinddir(e)
835 | ExprKind::Telldir(e) => {
836 self.analyze_expr(e);
837 }
838 ExprKind::Exit(Some(e)) | ExprKind::Rand(Some(e)) | ExprKind::Eof(Some(e)) => {
839 self.analyze_expr(e);
840 }
841 ExprKind::Mkdir { path, mode } => {
842 self.analyze_expr(path);
843 if let Some(m) = mode {
844 self.analyze_expr(m);
845 }
846 }
847 ExprKind::Rename { old, new }
848 | ExprKind::Link { old, new }
849 | ExprKind::Symlink { old, new } => {
850 self.analyze_expr(old);
851 self.analyze_expr(new);
852 }
853 ExprKind::Chown(files) => {
854 for f in files {
855 self.analyze_expr(f);
856 }
857 }
858 ExprKind::Substr {
859 string,
860 offset,
861 length,
862 replacement,
863 } => {
864 self.analyze_expr(string);
865 self.analyze_expr(offset);
866 if let Some(l) = length {
867 self.analyze_expr(l);
868 }
869 if let Some(r) = replacement {
870 self.analyze_expr(r);
871 }
872 }
873 ExprKind::Index {
874 string,
875 substr,
876 position,
877 }
878 | ExprKind::Rindex {
879 string,
880 substr,
881 position,
882 } => {
883 self.analyze_expr(string);
884 self.analyze_expr(substr);
885 if let Some(p) = position {
886 self.analyze_expr(p);
887 }
888 }
889 ExprKind::Sprintf { format, args } => {
890 self.analyze_expr(format);
891 for a in args {
892 self.analyze_expr(a);
893 }
894 }
895 ExprKind::Bless { ref_expr, class } => {
896 self.analyze_expr(ref_expr);
897 if let Some(c) = class {
898 self.analyze_expr(c);
899 }
900 }
901 _ => {}
902 }
903 }
904}
905
906fn is_special_var(name: &str) -> bool {
907 if name.len() == 1 {
908 return true;
909 }
910 matches!(
911 name,
912 "ARGV"
913 | "ENV"
914 | "SIG"
915 | "INC"
916 | "AUTOLOAD"
917 | "STDERR"
918 | "STDIN"
919 | "STDOUT"
920 | "DATA"
921 | "UNIVERSAL"
922 | "VERSION"
923 | "ISA"
924 | "EXPORT"
925 | "EXPORT_OK"
926 )
927}
928
929pub fn analyze_program(program: &Program, file: &str) -> PerlResult<()> {
930 StaticAnalyzer::new(file).analyze(program)
931}
932
933#[cfg(test)]
934mod tests {
935 use super::*;
936 use crate::parse_with_file;
937
938 fn lint(code: &str) -> PerlResult<()> {
939 let prog = parse_with_file(code, "test.stk").expect("parse");
940 analyze_program(&prog, "test.stk")
941 }
942
943 #[test]
944 fn undefined_scalar_detected() {
945 let r = lint("say $undefined");
946 assert!(r.is_err());
947 let e = r.unwrap_err();
948 assert_eq!(e.kind, ErrorKind::UndefinedVariable);
949 assert!(e.message.contains("$undefined"));
950 }
951
952 #[test]
953 fn defined_scalar_ok() {
954 assert!(lint("my $x = 1; say $x").is_ok());
955 }
956
957 #[test]
958 fn undefined_sub_detected() {
959 let r = lint("nonexistent_function()");
960 assert!(r.is_err());
961 let e = r.unwrap_err();
962 assert_eq!(e.kind, ErrorKind::UndefinedSubroutine);
963 assert!(e.message.contains("nonexistent_function"));
964 }
965
966 #[test]
967 fn defined_sub_ok() {
968 assert!(lint("sub foo { 1 } foo()").is_ok());
969 }
970
971 #[test]
972 fn builtin_sub_ok() {
973 assert!(lint("say 'hello'").is_ok());
974 assert!(lint("print 'hello'").is_ok());
975 assert!(lint("my @x = map { $_ * 2 } 1..3").is_ok());
976 }
977
978 #[test]
979 fn special_vars_ok() {
980 assert!(lint("say $_").is_ok());
981 assert!(lint("say @_").is_ok());
982 assert!(lint("say $a <=> $b").is_ok());
983 }
984
985 #[test]
986 fn foreach_var_in_scope() {
987 assert!(lint("foreach my $i (1..3) { say $i; }").is_ok());
988 }
989
990 #[test]
991 fn sub_params_in_scope() {
992 assert!(lint("sub foo($x) { say $x; } foo(1)").is_ok());
993 }
994
995 #[test]
996 fn assignment_declares_var() {
997 assert!(lint("$x = 1; say $x").is_ok());
998 }
999
1000 #[test]
1001 fn builtin_inc_ok() {
1002 assert!(lint("my $x = 1; inc($x)").is_ok());
1003 }
1004
1005 #[test]
1006 fn builtin_dec_ok() {
1007 assert!(lint("my $x = 1; dec($x)").is_ok());
1008 }
1009
1010 #[test]
1011 fn builtin_rev_ok() {
1012 assert!(lint("my $s = rev 'hello'").is_ok());
1013 }
1014
1015 #[test]
1016 fn builtin_p_alias_for_say_ok() {
1017 assert!(lint("p 'hello'").is_ok());
1018 }
1019
1020 #[test]
1021 fn builtin_t_thread_ok() {
1022 assert!(lint("t 1 inc inc").is_ok());
1023 }
1024
1025 #[test]
1026 fn thread_with_undefined_var_detected() {
1027 let r = lint("t $undefined inc");
1028 assert!(r.is_err());
1029 }
1030
1031 #[test]
1032 fn try_catch_var_in_scope() {
1033 assert!(lint("try { die 'err'; } catch ($e) { say $e; }").is_ok());
1034 }
1035
1036 #[test]
1037 fn interpolated_string_undefined_var() {
1038 let r = lint(r#"say "hello $undefined""#);
1039 assert!(r.is_err());
1040 }
1041
1042 #[test]
1043 fn interpolated_string_defined_var_ok() {
1044 assert!(lint(r#"my $x = 1; say "hello $x""#).is_ok());
1045 }
1046
1047 #[test]
1048 fn coderef_params_in_scope() {
1049 assert!(lint("my $f = sub ($x) { say $x; }; $f->(1)").is_ok());
1050 }
1051
1052 #[test]
1053 fn nested_sub_scope() {
1054 assert!(lint("sub outer { my $x = 1; sub inner { say $x; } }").is_ok());
1055 }
1056
1057 #[test]
1058 fn hash_element_access_ok() {
1059 assert!(lint("my %h = (a => 1); say $h{a}").is_ok());
1060 }
1061
1062 #[test]
1063 fn array_element_access_ok() {
1064 assert!(lint("my @a = (1, 2, 3); say $a[0]").is_ok());
1065 }
1066
1067 #[test]
1068 fn undefined_hash_detected() {
1069 let r = lint("say $undefined_hash{key}");
1070 assert!(r.is_err());
1071 }
1072
1073 #[test]
1074 fn undefined_array_detected() {
1075 let r = lint("say $undefined_array[0]");
1076 assert!(r.is_err());
1077 }
1078
1079 #[test]
1080 fn map_with_topic_ok() {
1081 assert!(lint("my @x = map { $_ * 2 } 1..3").is_ok());
1082 }
1083
1084 #[test]
1085 fn grep_with_topic_ok() {
1086 assert!(lint("my @x = grep { $_ > 1 } 1..3").is_ok());
1087 }
1088
1089 #[test]
1090 fn sort_with_ab_ok() {
1091 assert!(lint("my @x = sort { $a <=> $b } 1..3").is_ok());
1092 }
1093
1094 #[test]
1095 fn ternary_undefined_var_detected() {
1096 let r = lint("my $x = $undefined ? 1 : 0");
1097 assert!(r.is_err());
1098 }
1099
1100 #[test]
1101 fn binop_undefined_var_detected() {
1102 let r = lint("my $x = 1 + $undefined");
1103 assert!(r.is_err());
1104 }
1105
1106 #[test]
1107 fn postfix_if_undefined_detected() {
1108 let r = lint("say 'x' if $undefined");
1109 assert!(r.is_err());
1110 }
1111
1112 #[test]
1113 fn while_loop_var_ok() {
1114 assert!(lint("my $i = 0; while ($i < 10) { say $i; $i++; }").is_ok());
1115 }
1116
1117 #[test]
1118 fn for_loop_init_var_in_scope() {
1119 assert!(lint("for (my $i = 0; $i < 10; $i++) { say $i; }").is_ok());
1120 }
1121
1122 #[test]
1123 fn given_when_ok() {
1124 assert!(lint("my $x = 1; given ($x) { when (1) { say 'one'; } }").is_ok());
1125 }
1126
1127 #[test]
1128 fn arrow_deref_ok() {
1129 assert!(lint("my $h = { a => 1 }; say $h->{a}").is_ok());
1130 }
1131
1132 #[test]
1133 fn method_call_ok() {
1134 assert!(lint("my $obj = bless {}, 'Foo'; $obj->method()").is_ok());
1135 }
1136
1137 #[test]
1138 fn push_builtin_ok() {
1139 assert!(lint("my @a; push @a, 1, 2, 3").is_ok());
1140 }
1141
1142 #[test]
1143 fn splice_builtin_ok() {
1144 assert!(lint("my @a = (1, 2, 3); splice @a, 1, 1, 'x'").is_ok());
1145 }
1146
1147 #[test]
1148 fn substr_builtin_ok() {
1149 assert!(lint("my $s = 'hello'; say substr($s, 0, 2)").is_ok());
1150 }
1151
1152 #[test]
1153 fn sprintf_builtin_ok() {
1154 assert!(lint("my $s = sprintf('%d', 42)").is_ok());
1155 }
1156
1157 #[test]
1158 fn range_ok() {
1159 assert!(lint("my @a = 1..10").is_ok());
1160 }
1161
1162 #[test]
1163 fn qw_ok() {
1164 assert!(lint("my @a = qw(a b c)").is_ok());
1165 }
1166
1167 #[test]
1168 fn regex_ok() {
1169 assert!(lint("my $x = 'hello'; $x =~ /ell/").is_ok());
1170 }
1171
1172 #[test]
1173 fn anonymous_sub_captures_outer_var() {
1174 assert!(lint("my $x = 1; my $f = sub { say $x; }").is_ok());
1175 }
1176
1177 #[test]
1178 fn state_var_ok() {
1179 assert!(lint("sub counter { state $n = 0; $n++; }").is_ok());
1180 }
1181
1182 #[test]
1183 fn our_var_ok() {
1184 assert!(lint("our $VERSION = '1.0'").is_ok());
1185 }
1186
1187 #[test]
1188 fn local_var_ok() {
1189 assert!(lint("local $/ = undef").is_ok());
1190 }
1191
1192 #[test]
1193 fn chained_method_calls_ok() {
1194 assert!(lint("my $x = Foo->new->bar->baz").is_ok());
1195 }
1196
1197 #[test]
1198 fn list_assignment_ok() {
1199 assert!(lint("my ($a, $b, $c) = (1, 2, 3); say $a + $b + $c").is_ok());
1200 }
1201
1202 #[test]
1203 fn hash_slice_ok() {
1204 assert!(lint("my %h = (a => 1, b => 2); my @v = @h{qw(a b)}").is_ok());
1205 }
1206
1207 #[test]
1208 fn array_slice_ok() {
1209 assert!(lint("my @a = (1, 2, 3, 4); my @b = @a[0, 2]").is_ok());
1210 }
1211}