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