1use std::collections::HashMap;
6
7use super::functions::*;
8use std::collections::{HashSet, VecDeque};
9
10#[derive(Debug, Clone, PartialEq)]
12pub struct PHPParam {
13 pub name: std::string::String,
15 pub ty: Option<PHPType>,
17 pub default: Option<PHPExpr>,
19 pub by_ref: bool,
21 pub variadic: bool,
23 pub promoted: Option<PHPVisibility>,
25}
26impl PHPParam {
27 pub fn simple(name: impl Into<std::string::String>) -> Self {
29 PHPParam {
30 name: name.into(),
31 ty: None,
32 default: None,
33 by_ref: false,
34 variadic: false,
35 promoted: None,
36 }
37 }
38 pub fn typed(name: impl Into<std::string::String>, ty: PHPType) -> Self {
40 PHPParam {
41 name: name.into(),
42 ty: Some(ty),
43 default: None,
44 by_ref: false,
45 variadic: false,
46 promoted: None,
47 }
48 }
49 pub fn with_default(
51 name: impl Into<std::string::String>,
52 ty: Option<PHPType>,
53 default: PHPExpr,
54 ) -> Self {
55 PHPParam {
56 name: name.into(),
57 ty,
58 default: Some(default),
59 by_ref: false,
60 variadic: false,
61 promoted: None,
62 }
63 }
64}
65#[derive(Debug, Clone)]
67pub struct PHPScript {
68 pub strict_types: bool,
70 pub namespace: Option<std::string::String>,
72 pub uses: Vec<(std::string::String, Option<std::string::String>)>,
74 pub functions: Vec<PHPFunction>,
76 pub classes: Vec<PHPClass>,
78 pub interfaces: Vec<PHPInterface>,
80 pub traits: Vec<PHPTrait>,
82 pub enums: Vec<PHPEnum>,
84 pub main: Vec<std::string::String>,
86}
87impl PHPScript {
88 pub fn new() -> Self {
90 PHPScript {
91 strict_types: true,
92 namespace: None,
93 uses: vec![],
94 functions: vec![],
95 classes: vec![],
96 interfaces: vec![],
97 traits: vec![],
98 enums: vec![],
99 main: vec![],
100 }
101 }
102}
103#[allow(dead_code)]
105#[derive(Debug, Clone, Default)]
106pub struct PHPExtPassStats {
107 pub iterations: usize,
108 pub changed: bool,
109 pub nodes_visited: usize,
110 pub nodes_modified: usize,
111 pub time_ms: u64,
112 pub memory_bytes: usize,
113 pub errors: usize,
114}
115impl PHPExtPassStats {
116 #[allow(dead_code)]
117 pub fn new() -> Self {
118 Self::default()
119 }
120 #[allow(dead_code)]
121 pub fn visit(&mut self) {
122 self.nodes_visited += 1;
123 }
124 #[allow(dead_code)]
125 pub fn modify(&mut self) {
126 self.nodes_modified += 1;
127 self.changed = true;
128 }
129 #[allow(dead_code)]
130 pub fn iterate(&mut self) {
131 self.iterations += 1;
132 }
133 #[allow(dead_code)]
134 pub fn error(&mut self) {
135 self.errors += 1;
136 }
137 #[allow(dead_code)]
138 pub fn efficiency(&self) -> f64 {
139 if self.nodes_visited == 0 {
140 0.0
141 } else {
142 self.nodes_modified as f64 / self.nodes_visited as f64
143 }
144 }
145 #[allow(dead_code)]
146 pub fn merge(&mut self, o: &PHPExtPassStats) {
147 self.iterations += o.iterations;
148 self.changed |= o.changed;
149 self.nodes_visited += o.nodes_visited;
150 self.nodes_modified += o.nodes_modified;
151 self.time_ms += o.time_ms;
152 self.memory_bytes = self.memory_bytes.max(o.memory_bytes);
153 self.errors += o.errors;
154 }
155}
156#[allow(dead_code)]
158#[derive(Debug, Clone)]
159pub struct PHPExtWorklist {
160 pub(super) items: std::collections::VecDeque<usize>,
161 pub(super) present: Vec<bool>,
162}
163impl PHPExtWorklist {
164 #[allow(dead_code)]
165 pub fn new(capacity: usize) -> Self {
166 Self {
167 items: std::collections::VecDeque::new(),
168 present: vec![false; capacity],
169 }
170 }
171 #[allow(dead_code)]
172 pub fn push(&mut self, id: usize) {
173 if id < self.present.len() && !self.present[id] {
174 self.present[id] = true;
175 self.items.push_back(id);
176 }
177 }
178 #[allow(dead_code)]
179 pub fn push_front(&mut self, id: usize) {
180 if id < self.present.len() && !self.present[id] {
181 self.present[id] = true;
182 self.items.push_front(id);
183 }
184 }
185 #[allow(dead_code)]
186 pub fn pop(&mut self) -> Option<usize> {
187 let id = self.items.pop_front()?;
188 if id < self.present.len() {
189 self.present[id] = false;
190 }
191 Some(id)
192 }
193 #[allow(dead_code)]
194 pub fn is_empty(&self) -> bool {
195 self.items.is_empty()
196 }
197 #[allow(dead_code)]
198 pub fn len(&self) -> usize {
199 self.items.len()
200 }
201 #[allow(dead_code)]
202 pub fn contains(&self, id: usize) -> bool {
203 id < self.present.len() && self.present[id]
204 }
205 #[allow(dead_code)]
206 pub fn drain_all(&mut self) -> Vec<usize> {
207 let v: Vec<usize> = self.items.drain(..).collect();
208 for &id in &v {
209 if id < self.present.len() {
210 self.present[id] = false;
211 }
212 }
213 v
214 }
215}
216#[derive(Debug, Clone, PartialEq)]
218pub struct PHPTrait {
219 pub name: std::string::String,
221 pub properties: Vec<PHPProperty>,
223 pub methods: Vec<PHPFunction>,
225}
226impl PHPTrait {
227 pub fn new(name: impl Into<std::string::String>) -> Self {
229 PHPTrait {
230 name: name.into(),
231 properties: vec![],
232 methods: vec![],
233 }
234 }
235}
236#[allow(dead_code)]
237#[derive(Debug, Clone)]
238pub struct PHPAnalysisCache {
239 pub(super) entries: std::collections::HashMap<String, PHPCacheEntry>,
240 pub(super) max_size: usize,
241 pub(super) hits: u64,
242 pub(super) misses: u64,
243}
244impl PHPAnalysisCache {
245 #[allow(dead_code)]
246 pub fn new(max_size: usize) -> Self {
247 PHPAnalysisCache {
248 entries: std::collections::HashMap::new(),
249 max_size,
250 hits: 0,
251 misses: 0,
252 }
253 }
254 #[allow(dead_code)]
255 pub fn get(&mut self, key: &str) -> Option<&PHPCacheEntry> {
256 if self.entries.contains_key(key) {
257 self.hits += 1;
258 self.entries.get(key)
259 } else {
260 self.misses += 1;
261 None
262 }
263 }
264 #[allow(dead_code)]
265 pub fn insert(&mut self, key: String, data: Vec<u8>) {
266 if self.entries.len() >= self.max_size {
267 if let Some(oldest) = self.entries.keys().next().cloned() {
268 self.entries.remove(&oldest);
269 }
270 }
271 self.entries.insert(
272 key.clone(),
273 PHPCacheEntry {
274 key,
275 data,
276 timestamp: 0,
277 valid: true,
278 },
279 );
280 }
281 #[allow(dead_code)]
282 pub fn invalidate(&mut self, key: &str) {
283 if let Some(entry) = self.entries.get_mut(key) {
284 entry.valid = false;
285 }
286 }
287 #[allow(dead_code)]
288 pub fn clear(&mut self) {
289 self.entries.clear();
290 }
291 #[allow(dead_code)]
292 pub fn hit_rate(&self) -> f64 {
293 let total = self.hits + self.misses;
294 if total == 0 {
295 return 0.0;
296 }
297 self.hits as f64 / total as f64
298 }
299 #[allow(dead_code)]
300 pub fn size(&self) -> usize {
301 self.entries.len()
302 }
303}
304#[derive(Debug, Clone, PartialEq)]
306pub struct PHPClass {
307 pub name: std::string::String,
309 pub parent: Option<std::string::String>,
311 pub interfaces: Vec<std::string::String>,
313 pub traits: Vec<std::string::String>,
315 pub is_abstract: bool,
317 pub is_final: bool,
319 pub is_readonly: bool,
321 pub properties: Vec<PHPProperty>,
323 pub methods: Vec<PHPFunction>,
325 pub constants: Vec<(std::string::String, PHPType, std::string::String)>,
327}
328impl PHPClass {
329 pub fn new(name: impl Into<std::string::String>) -> Self {
331 PHPClass {
332 name: name.into(),
333 parent: None,
334 interfaces: vec![],
335 traits: vec![],
336 is_abstract: false,
337 is_final: false,
338 is_readonly: false,
339 properties: vec![],
340 methods: vec![],
341 constants: vec![],
342 }
343 }
344 pub fn abstract_class(name: impl Into<std::string::String>) -> Self {
346 let mut cls = PHPClass::new(name);
347 cls.is_abstract = true;
348 cls
349 }
350}
351#[allow(dead_code)]
352#[derive(Debug, Clone)]
353pub struct PHPWorklist {
354 pub(super) items: std::collections::VecDeque<u32>,
355 pub(super) in_worklist: std::collections::HashSet<u32>,
356}
357impl PHPWorklist {
358 #[allow(dead_code)]
359 pub fn new() -> Self {
360 PHPWorklist {
361 items: std::collections::VecDeque::new(),
362 in_worklist: std::collections::HashSet::new(),
363 }
364 }
365 #[allow(dead_code)]
366 pub fn push(&mut self, item: u32) -> bool {
367 if self.in_worklist.insert(item) {
368 self.items.push_back(item);
369 true
370 } else {
371 false
372 }
373 }
374 #[allow(dead_code)]
375 pub fn pop(&mut self) -> Option<u32> {
376 let item = self.items.pop_front()?;
377 self.in_worklist.remove(&item);
378 Some(item)
379 }
380 #[allow(dead_code)]
381 pub fn is_empty(&self) -> bool {
382 self.items.is_empty()
383 }
384 #[allow(dead_code)]
385 pub fn len(&self) -> usize {
386 self.items.len()
387 }
388 #[allow(dead_code)]
389 pub fn contains(&self, item: u32) -> bool {
390 self.in_worklist.contains(&item)
391 }
392}
393#[allow(dead_code)]
394pub struct PHPPassRegistry {
395 pub(super) configs: Vec<PHPPassConfig>,
396 pub(super) stats: std::collections::HashMap<String, PHPPassStats>,
397}
398impl PHPPassRegistry {
399 #[allow(dead_code)]
400 pub fn new() -> Self {
401 PHPPassRegistry {
402 configs: Vec::new(),
403 stats: std::collections::HashMap::new(),
404 }
405 }
406 #[allow(dead_code)]
407 pub fn register(&mut self, config: PHPPassConfig) {
408 self.stats
409 .insert(config.pass_name.clone(), PHPPassStats::new());
410 self.configs.push(config);
411 }
412 #[allow(dead_code)]
413 pub fn enabled_passes(&self) -> Vec<&PHPPassConfig> {
414 self.configs.iter().filter(|c| c.enabled).collect()
415 }
416 #[allow(dead_code)]
417 pub fn get_stats(&self, name: &str) -> Option<&PHPPassStats> {
418 self.stats.get(name)
419 }
420 #[allow(dead_code)]
421 pub fn total_passes(&self) -> usize {
422 self.configs.len()
423 }
424 #[allow(dead_code)]
425 pub fn enabled_count(&self) -> usize {
426 self.enabled_passes().len()
427 }
428 #[allow(dead_code)]
429 pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
430 if let Some(stats) = self.stats.get_mut(name) {
431 stats.record_run(changes, time_ms, iter);
432 }
433 }
434}
435#[allow(dead_code)]
436#[derive(Debug, Clone)]
437pub struct PHPLivenessInfo {
438 pub live_in: Vec<std::collections::HashSet<u32>>,
439 pub live_out: Vec<std::collections::HashSet<u32>>,
440 pub defs: Vec<std::collections::HashSet<u32>>,
441 pub uses: Vec<std::collections::HashSet<u32>>,
442}
443impl PHPLivenessInfo {
444 #[allow(dead_code)]
445 pub fn new(block_count: usize) -> Self {
446 PHPLivenessInfo {
447 live_in: vec![std::collections::HashSet::new(); block_count],
448 live_out: vec![std::collections::HashSet::new(); block_count],
449 defs: vec![std::collections::HashSet::new(); block_count],
450 uses: vec![std::collections::HashSet::new(); block_count],
451 }
452 }
453 #[allow(dead_code)]
454 pub fn add_def(&mut self, block: usize, var: u32) {
455 if block < self.defs.len() {
456 self.defs[block].insert(var);
457 }
458 }
459 #[allow(dead_code)]
460 pub fn add_use(&mut self, block: usize, var: u32) {
461 if block < self.uses.len() {
462 self.uses[block].insert(var);
463 }
464 }
465 #[allow(dead_code)]
466 pub fn is_live_in(&self, block: usize, var: u32) -> bool {
467 self.live_in
468 .get(block)
469 .map(|s| s.contains(&var))
470 .unwrap_or(false)
471 }
472 #[allow(dead_code)]
473 pub fn is_live_out(&self, block: usize, var: u32) -> bool {
474 self.live_out
475 .get(block)
476 .map(|s| s.contains(&var))
477 .unwrap_or(false)
478 }
479}
480#[allow(dead_code)]
482#[derive(Debug, Default)]
483pub struct PHPExtPassRegistry {
484 pub(super) configs: Vec<PHPExtPassConfig>,
485 pub(super) stats: Vec<PHPExtPassStats>,
486}
487impl PHPExtPassRegistry {
488 #[allow(dead_code)]
489 pub fn new() -> Self {
490 Self::default()
491 }
492 #[allow(dead_code)]
493 pub fn register(&mut self, c: PHPExtPassConfig) {
494 self.stats.push(PHPExtPassStats::new());
495 self.configs.push(c);
496 }
497 #[allow(dead_code)]
498 pub fn len(&self) -> usize {
499 self.configs.len()
500 }
501 #[allow(dead_code)]
502 pub fn is_empty(&self) -> bool {
503 self.configs.is_empty()
504 }
505 #[allow(dead_code)]
506 pub fn get(&self, i: usize) -> Option<&PHPExtPassConfig> {
507 self.configs.get(i)
508 }
509 #[allow(dead_code)]
510 pub fn get_stats(&self, i: usize) -> Option<&PHPExtPassStats> {
511 self.stats.get(i)
512 }
513 #[allow(dead_code)]
514 pub fn enabled_passes(&self) -> Vec<&PHPExtPassConfig> {
515 self.configs.iter().filter(|c| c.enabled).collect()
516 }
517 #[allow(dead_code)]
518 pub fn passes_in_phase(&self, ph: &PHPExtPassPhase) -> Vec<&PHPExtPassConfig> {
519 self.configs
520 .iter()
521 .filter(|c| c.enabled && &c.phase == ph)
522 .collect()
523 }
524 #[allow(dead_code)]
525 pub fn total_nodes_visited(&self) -> usize {
526 self.stats.iter().map(|s| s.nodes_visited).sum()
527 }
528 #[allow(dead_code)]
529 pub fn any_changed(&self) -> bool {
530 self.stats.iter().any(|s| s.changed)
531 }
532}
533#[allow(dead_code)]
534#[derive(Debug, Clone)]
535pub struct PHPDominatorTree {
536 pub idom: Vec<Option<u32>>,
537 pub dom_children: Vec<Vec<u32>>,
538 pub dom_depth: Vec<u32>,
539}
540impl PHPDominatorTree {
541 #[allow(dead_code)]
542 pub fn new(size: usize) -> Self {
543 PHPDominatorTree {
544 idom: vec![None; size],
545 dom_children: vec![Vec::new(); size],
546 dom_depth: vec![0; size],
547 }
548 }
549 #[allow(dead_code)]
550 pub fn set_idom(&mut self, node: usize, idom: u32) {
551 self.idom[node] = Some(idom);
552 }
553 #[allow(dead_code)]
554 pub fn dominates(&self, a: usize, b: usize) -> bool {
555 if a == b {
556 return true;
557 }
558 let mut cur = b;
559 loop {
560 match self.idom[cur] {
561 Some(parent) if parent as usize == a => return true,
562 Some(parent) if parent as usize == cur => return false,
563 Some(parent) => cur = parent as usize,
564 None => return false,
565 }
566 }
567 }
568 #[allow(dead_code)]
569 pub fn depth(&self, node: usize) -> u32 {
570 self.dom_depth.get(node).copied().unwrap_or(0)
571 }
572}
573#[allow(dead_code)]
575#[derive(Debug, Clone, Default)]
576pub struct PHPExtLiveness {
577 pub live_in: Vec<Vec<usize>>,
578 pub live_out: Vec<Vec<usize>>,
579 pub defs: Vec<Vec<usize>>,
580 pub uses: Vec<Vec<usize>>,
581}
582impl PHPExtLiveness {
583 #[allow(dead_code)]
584 pub fn new(n: usize) -> Self {
585 Self {
586 live_in: vec![Vec::new(); n],
587 live_out: vec![Vec::new(); n],
588 defs: vec![Vec::new(); n],
589 uses: vec![Vec::new(); n],
590 }
591 }
592 #[allow(dead_code)]
593 pub fn live_in(&self, b: usize, v: usize) -> bool {
594 self.live_in.get(b).map(|s| s.contains(&v)).unwrap_or(false)
595 }
596 #[allow(dead_code)]
597 pub fn live_out(&self, b: usize, v: usize) -> bool {
598 self.live_out
599 .get(b)
600 .map(|s| s.contains(&v))
601 .unwrap_or(false)
602 }
603 #[allow(dead_code)]
604 pub fn add_def(&mut self, b: usize, v: usize) {
605 if let Some(s) = self.defs.get_mut(b) {
606 if !s.contains(&v) {
607 s.push(v);
608 }
609 }
610 }
611 #[allow(dead_code)]
612 pub fn add_use(&mut self, b: usize, v: usize) {
613 if let Some(s) = self.uses.get_mut(b) {
614 if !s.contains(&v) {
615 s.push(v);
616 }
617 }
618 }
619 #[allow(dead_code)]
620 pub fn var_is_used_in_block(&self, b: usize, v: usize) -> bool {
621 self.uses.get(b).map(|s| s.contains(&v)).unwrap_or(false)
622 }
623 #[allow(dead_code)]
624 pub fn var_is_def_in_block(&self, b: usize, v: usize) -> bool {
625 self.defs.get(b).map(|s| s.contains(&v)).unwrap_or(false)
626 }
627}
628#[allow(dead_code)]
630#[derive(Debug, Clone, Default)]
631pub struct PHPExtConstFolder {
632 pub(super) folds: usize,
633 pub(super) failures: usize,
634 pub(super) enabled: bool,
635}
636impl PHPExtConstFolder {
637 #[allow(dead_code)]
638 pub fn new() -> Self {
639 Self {
640 folds: 0,
641 failures: 0,
642 enabled: true,
643 }
644 }
645 #[allow(dead_code)]
646 pub fn add_i64(&mut self, a: i64, b: i64) -> Option<i64> {
647 self.folds += 1;
648 a.checked_add(b)
649 }
650 #[allow(dead_code)]
651 pub fn sub_i64(&mut self, a: i64, b: i64) -> Option<i64> {
652 self.folds += 1;
653 a.checked_sub(b)
654 }
655 #[allow(dead_code)]
656 pub fn mul_i64(&mut self, a: i64, b: i64) -> Option<i64> {
657 self.folds += 1;
658 a.checked_mul(b)
659 }
660 #[allow(dead_code)]
661 pub fn div_i64(&mut self, a: i64, b: i64) -> Option<i64> {
662 if b == 0 {
663 self.failures += 1;
664 None
665 } else {
666 self.folds += 1;
667 a.checked_div(b)
668 }
669 }
670 #[allow(dead_code)]
671 pub fn rem_i64(&mut self, a: i64, b: i64) -> Option<i64> {
672 if b == 0 {
673 self.failures += 1;
674 None
675 } else {
676 self.folds += 1;
677 a.checked_rem(b)
678 }
679 }
680 #[allow(dead_code)]
681 pub fn neg_i64(&mut self, a: i64) -> Option<i64> {
682 self.folds += 1;
683 a.checked_neg()
684 }
685 #[allow(dead_code)]
686 pub fn shl_i64(&mut self, a: i64, s: u32) -> Option<i64> {
687 if s >= 64 {
688 self.failures += 1;
689 None
690 } else {
691 self.folds += 1;
692 a.checked_shl(s)
693 }
694 }
695 #[allow(dead_code)]
696 pub fn shr_i64(&mut self, a: i64, s: u32) -> Option<i64> {
697 if s >= 64 {
698 self.failures += 1;
699 None
700 } else {
701 self.folds += 1;
702 a.checked_shr(s)
703 }
704 }
705 #[allow(dead_code)]
706 pub fn and_i64(&mut self, a: i64, b: i64) -> i64 {
707 self.folds += 1;
708 a & b
709 }
710 #[allow(dead_code)]
711 pub fn or_i64(&mut self, a: i64, b: i64) -> i64 {
712 self.folds += 1;
713 a | b
714 }
715 #[allow(dead_code)]
716 pub fn xor_i64(&mut self, a: i64, b: i64) -> i64 {
717 self.folds += 1;
718 a ^ b
719 }
720 #[allow(dead_code)]
721 pub fn not_i64(&mut self, a: i64) -> i64 {
722 self.folds += 1;
723 !a
724 }
725 #[allow(dead_code)]
726 pub fn fold_count(&self) -> usize {
727 self.folds
728 }
729 #[allow(dead_code)]
730 pub fn failure_count(&self) -> usize {
731 self.failures
732 }
733 #[allow(dead_code)]
734 pub fn enable(&mut self) {
735 self.enabled = true;
736 }
737 #[allow(dead_code)]
738 pub fn disable(&mut self) {
739 self.enabled = false;
740 }
741 #[allow(dead_code)]
742 pub fn is_enabled(&self) -> bool {
743 self.enabled
744 }
745}
746#[derive(Debug, Clone, PartialEq)]
748pub struct PHPFunction {
749 pub name: std::string::String,
751 pub params: Vec<PHPParam>,
753 pub return_type: Option<PHPType>,
755 pub body: Vec<std::string::String>,
757 pub is_static: bool,
759 pub is_abstract: bool,
761 pub visibility: Option<PHPVisibility>,
763 pub doc_comment: Option<std::string::String>,
765}
766impl PHPFunction {
767 pub fn new(name: impl Into<std::string::String>, body: Vec<std::string::String>) -> Self {
769 PHPFunction {
770 name: name.into(),
771 params: vec![],
772 return_type: None,
773 body,
774 is_static: false,
775 is_abstract: false,
776 visibility: None,
777 doc_comment: None,
778 }
779 }
780 pub fn method(
782 name: impl Into<std::string::String>,
783 visibility: PHPVisibility,
784 params: Vec<PHPParam>,
785 return_type: Option<PHPType>,
786 body: Vec<std::string::String>,
787 ) -> Self {
788 PHPFunction {
789 name: name.into(),
790 params,
791 return_type,
792 body,
793 is_static: false,
794 is_abstract: false,
795 visibility: Some(visibility),
796 doc_comment: None,
797 }
798 }
799}
800#[derive(Debug, Clone, PartialEq, Eq)]
802pub enum PHPVisibility {
803 Public,
804 Protected,
805 Private,
806}
807#[derive(Debug, Clone, PartialEq)]
809pub struct PHPEnumCase {
810 pub name: std::string::String,
812 pub value: Option<std::string::String>,
814}
815#[derive(Debug, Clone, PartialEq, Eq, Hash)]
817pub enum PHPType {
818 Int,
820 Float,
822 String,
824 Bool,
826 Array,
828 Null,
830 Mixed,
832 Callable,
834 Void,
836 Never,
838 Object,
840 Iterable,
842 Nullable(Box<PHPType>),
844 Union(Vec<PHPType>),
846 Intersection(Vec<PHPType>),
848 Named(std::string::String),
850 Self_,
852 Static,
854 Parent,
856}
857#[allow(dead_code)]
858#[derive(Debug, Clone)]
859pub struct PHPCacheEntry {
860 pub key: String,
861 pub data: Vec<u8>,
862 pub timestamp: u64,
863 pub valid: bool,
864}
865#[allow(dead_code)]
867#[derive(Debug, Clone, PartialEq, Eq, Hash)]
868pub enum PHPExtPassPhase {
869 Early,
870 Middle,
871 Late,
872 Finalize,
873}
874impl PHPExtPassPhase {
875 #[allow(dead_code)]
876 pub fn is_early(&self) -> bool {
877 matches!(self, Self::Early)
878 }
879 #[allow(dead_code)]
880 pub fn is_middle(&self) -> bool {
881 matches!(self, Self::Middle)
882 }
883 #[allow(dead_code)]
884 pub fn is_late(&self) -> bool {
885 matches!(self, Self::Late)
886 }
887 #[allow(dead_code)]
888 pub fn is_finalize(&self) -> bool {
889 matches!(self, Self::Finalize)
890 }
891 #[allow(dead_code)]
892 pub fn order(&self) -> u32 {
893 match self {
894 Self::Early => 0,
895 Self::Middle => 1,
896 Self::Late => 2,
897 Self::Finalize => 3,
898 }
899 }
900 #[allow(dead_code)]
901 pub fn from_order(n: u32) -> Option<Self> {
902 match n {
903 0 => Some(Self::Early),
904 1 => Some(Self::Middle),
905 2 => Some(Self::Late),
906 3 => Some(Self::Finalize),
907 _ => None,
908 }
909 }
910}
911#[allow(dead_code)]
912#[derive(Debug, Clone, PartialEq)]
913pub enum PHPPassPhase {
914 Analysis,
915 Transformation,
916 Verification,
917 Cleanup,
918}
919impl PHPPassPhase {
920 #[allow(dead_code)]
921 pub fn name(&self) -> &str {
922 match self {
923 PHPPassPhase::Analysis => "analysis",
924 PHPPassPhase::Transformation => "transformation",
925 PHPPassPhase::Verification => "verification",
926 PHPPassPhase::Cleanup => "cleanup",
927 }
928 }
929 #[allow(dead_code)]
930 pub fn is_modifying(&self) -> bool {
931 matches!(self, PHPPassPhase::Transformation | PHPPassPhase::Cleanup)
932 }
933}
934#[allow(dead_code)]
936#[derive(Debug, Clone)]
937pub struct PHPExtPassConfig {
938 pub name: String,
939 pub phase: PHPExtPassPhase,
940 pub enabled: bool,
941 pub max_iterations: usize,
942 pub debug: u32,
943 pub timeout_ms: Option<u64>,
944}
945impl PHPExtPassConfig {
946 #[allow(dead_code)]
947 pub fn new(name: impl Into<String>) -> Self {
948 Self {
949 name: name.into(),
950 phase: PHPExtPassPhase::Middle,
951 enabled: true,
952 max_iterations: 100,
953 debug: 0,
954 timeout_ms: None,
955 }
956 }
957 #[allow(dead_code)]
958 pub fn with_phase(mut self, phase: PHPExtPassPhase) -> Self {
959 self.phase = phase;
960 self
961 }
962 #[allow(dead_code)]
963 pub fn with_max_iter(mut self, n: usize) -> Self {
964 self.max_iterations = n;
965 self
966 }
967 #[allow(dead_code)]
968 pub fn with_debug(mut self, d: u32) -> Self {
969 self.debug = d;
970 self
971 }
972 #[allow(dead_code)]
973 pub fn disabled(mut self) -> Self {
974 self.enabled = false;
975 self
976 }
977 #[allow(dead_code)]
978 pub fn with_timeout(mut self, ms: u64) -> Self {
979 self.timeout_ms = Some(ms);
980 self
981 }
982 #[allow(dead_code)]
983 pub fn is_debug_enabled(&self) -> bool {
984 self.debug > 0
985 }
986}
987#[allow(dead_code)]
989#[derive(Debug)]
990pub struct PHPExtCache {
991 pub(super) entries: Vec<(u64, Vec<u8>, bool, u32)>,
992 pub(super) cap: usize,
993 pub(super) total_hits: u64,
994 pub(super) total_misses: u64,
995}
996impl PHPExtCache {
997 #[allow(dead_code)]
998 pub fn new(cap: usize) -> Self {
999 Self {
1000 entries: Vec::new(),
1001 cap,
1002 total_hits: 0,
1003 total_misses: 0,
1004 }
1005 }
1006 #[allow(dead_code)]
1007 pub fn get(&mut self, key: u64) -> Option<&[u8]> {
1008 for e in self.entries.iter_mut() {
1009 if e.0 == key && e.2 {
1010 e.3 += 1;
1011 self.total_hits += 1;
1012 return Some(&e.1);
1013 }
1014 }
1015 self.total_misses += 1;
1016 None
1017 }
1018 #[allow(dead_code)]
1019 pub fn put(&mut self, key: u64, data: Vec<u8>) {
1020 if self.entries.len() >= self.cap {
1021 self.entries.retain(|e| e.2);
1022 if self.entries.len() >= self.cap {
1023 self.entries.remove(0);
1024 }
1025 }
1026 self.entries.push((key, data, true, 0));
1027 }
1028 #[allow(dead_code)]
1029 pub fn invalidate(&mut self) {
1030 for e in self.entries.iter_mut() {
1031 e.2 = false;
1032 }
1033 }
1034 #[allow(dead_code)]
1035 pub fn hit_rate(&self) -> f64 {
1036 let t = self.total_hits + self.total_misses;
1037 if t == 0 {
1038 0.0
1039 } else {
1040 self.total_hits as f64 / t as f64
1041 }
1042 }
1043 #[allow(dead_code)]
1044 pub fn live_count(&self) -> usize {
1045 self.entries.iter().filter(|e| e.2).count()
1046 }
1047}
1048pub struct PHPBackend {
1050 pub(super) indent: std::string::String,
1052 pub(super) mangle_cache: HashMap<std::string::String, std::string::String>,
1054 pub(super) emit_docs: bool,
1056}
1057impl PHPBackend {
1058 pub fn new() -> Self {
1060 PHPBackend {
1061 indent: " ".to_string(),
1062 mangle_cache: HashMap::new(),
1063 emit_docs: true,
1064 }
1065 }
1066 pub fn with_indent(indent: impl Into<std::string::String>) -> Self {
1068 PHPBackend {
1069 indent: indent.into(),
1070 mangle_cache: HashMap::new(),
1071 emit_docs: true,
1072 }
1073 }
1074 pub fn emit_type(&self, ty: &PHPType) -> std::string::String {
1076 format!("{}", ty)
1077 }
1078 pub fn emit_expr(&self, expr: &PHPExpr) -> std::string::String {
1080 format!("{}", expr)
1081 }
1082 pub fn mangle_name(&self, name: &str) -> std::string::String {
1086 if let Some(cached) = self.mangle_cache.get(name) {
1087 return cached.clone();
1088 }
1089 let mut result = std::string::String::new();
1090 let mut first = true;
1091 for c in name.chars() {
1092 if first {
1093 if c.is_alphabetic() || c == '_' {
1094 result.push(c);
1095 } else {
1096 result.push('_');
1097 if c.is_alphanumeric() {
1098 result.push(c);
1099 }
1100 }
1101 first = false;
1102 } else {
1103 match c {
1104 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => result.push(c),
1105 '.' | ':' | '\'' => result.push('_'),
1106 '-' => result.push('_'),
1107 _ => {
1108 let code = c as u32;
1109 result.push_str(&format!("_u{:04X}_", code));
1110 }
1111 }
1112 }
1113 }
1114 let reserved = [
1115 "abstract",
1116 "and",
1117 "array",
1118 "as",
1119 "break",
1120 "callable",
1121 "case",
1122 "catch",
1123 "class",
1124 "clone",
1125 "const",
1126 "continue",
1127 "declare",
1128 "default",
1129 "die",
1130 "do",
1131 "echo",
1132 "else",
1133 "elseif",
1134 "empty",
1135 "enddeclare",
1136 "endfor",
1137 "endforeach",
1138 "endif",
1139 "endswitch",
1140 "endwhile",
1141 "enum",
1142 "eval",
1143 "exit",
1144 "extends",
1145 "final",
1146 "finally",
1147 "fn",
1148 "for",
1149 "foreach",
1150 "function",
1151 "global",
1152 "goto",
1153 "if",
1154 "implements",
1155 "include",
1156 "include_once",
1157 "instanceof",
1158 "insteadof",
1159 "interface",
1160 "isset",
1161 "list",
1162 "match",
1163 "namespace",
1164 "new",
1165 "null",
1166 "or",
1167 "print",
1168 "private",
1169 "protected",
1170 "public",
1171 "readonly",
1172 "require",
1173 "require_once",
1174 "return",
1175 "static",
1176 "switch",
1177 "throw",
1178 "trait",
1179 "try",
1180 "unset",
1181 "use",
1182 "var",
1183 "while",
1184 "xor",
1185 "yield",
1186 ];
1187 if reserved.contains(&result.as_str()) {
1188 result.push_str("_ox");
1189 }
1190 result
1191 }
1192 pub(super) fn emit_param(&self, param: &PHPParam) -> std::string::String {
1194 format_param(param)
1195 }
1196 pub fn emit_function(&self, func: &PHPFunction) -> std::string::String {
1198 let mut out = std::string::String::new();
1199 if self.emit_docs {
1200 if let Some(doc) = &func.doc_comment {
1201 out.push_str("/**\n");
1202 for line in doc.lines() {
1203 out.push_str(&format!(" * {}\n", line));
1204 }
1205 out.push_str(" */\n");
1206 }
1207 }
1208 if let Some(vis) = &func.visibility {
1209 out.push_str(&format!("{} ", vis));
1210 }
1211 if func.is_static {
1212 out.push_str("static ");
1213 }
1214 if func.is_abstract {
1215 out.push_str("abstract ");
1216 }
1217 let params_s: Vec<std::string::String> =
1218 func.params.iter().map(|p| self.emit_param(p)).collect();
1219 out.push_str(&format!("function {}({})", func.name, params_s.join(", ")));
1220 if let Some(ret) = &func.return_type {
1221 out.push_str(&format!(": {}", ret));
1222 }
1223 if func.is_abstract {
1224 out.push_str(";\n");
1225 } else {
1226 out.push_str("\n{\n");
1227 for line in &func.body {
1228 out.push_str(&format!("{}{}\n", self.indent, line));
1229 }
1230 out.push_str("}\n");
1231 }
1232 out
1233 }
1234 pub(super) fn emit_property(&self, prop: &PHPProperty) -> std::string::String {
1236 let mut s = format!("{} ", prop.visibility);
1237 if prop.is_static {
1238 s.push_str("static ");
1239 }
1240 if prop.readonly {
1241 s.push_str("readonly ");
1242 }
1243 if let Some(ty) = &prop.ty {
1244 s.push_str(&format!("{} ", ty));
1245 }
1246 s.push_str(&format!("${}", prop.name));
1247 if let Some(default) = &prop.default {
1248 s.push_str(&format!(" = {}", default));
1249 }
1250 s.push(';');
1251 s
1252 }
1253 pub fn emit_interface(&self, iface: &PHPInterface) -> std::string::String {
1255 let mut out = std::string::String::new();
1256 out.push_str(&format!("interface {}", iface.name));
1257 if !iface.extends.is_empty() {
1258 out.push_str(&format!(" extends {}", iface.extends.join(", ")));
1259 }
1260 out.push_str("\n{\n");
1261 for (name, val) in &iface.constants {
1262 out.push_str(&format!("{}const {} = {};\n", self.indent, name, val));
1263 }
1264 for method in &iface.methods {
1265 out.push_str(&self.indent_block(&self.emit_function(method)));
1266 }
1267 out.push_str("}\n");
1268 out
1269 }
1270 pub fn emit_trait(&self, tr: &PHPTrait) -> std::string::String {
1272 let mut out = std::string::String::new();
1273 out.push_str(&format!("trait {}\n{{\n", tr.name));
1274 for prop in &tr.properties {
1275 out.push_str(&format!("{}{}\n", self.indent, self.emit_property(prop)));
1276 }
1277 for method in &tr.methods {
1278 out.push_str(&self.indent_block(&self.emit_function(method)));
1279 }
1280 out.push_str("}\n");
1281 out
1282 }
1283 pub fn emit_enum(&self, en: &PHPEnum) -> std::string::String {
1285 let mut out = std::string::String::new();
1286 out.push_str(&format!("enum {}", en.name));
1287 if let Some(bt) = &en.backing_type {
1288 out.push_str(&format!(": {}", bt));
1289 }
1290 if !en.implements.is_empty() {
1291 out.push_str(&format!(" implements {}", en.implements.join(", ")));
1292 }
1293 out.push_str("\n{\n");
1294 for case in &en.cases {
1295 if let Some(val) = &case.value {
1296 out.push_str(&format!("{}case {} = {};\n", self.indent, case.name, val));
1297 } else {
1298 out.push_str(&format!("{}case {};\n", self.indent, case.name));
1299 }
1300 }
1301 for method in &en.methods {
1302 out.push_str(&self.indent_block(&self.emit_function(method)));
1303 }
1304 out.push_str("}\n");
1305 out
1306 }
1307 pub fn emit_class(&self, class: &PHPClass) -> std::string::String {
1309 let mut out = std::string::String::new();
1310 if class.is_abstract {
1311 out.push_str("abstract ");
1312 }
1313 if class.is_final {
1314 out.push_str("final ");
1315 }
1316 if class.is_readonly {
1317 out.push_str("readonly ");
1318 }
1319 out.push_str(&format!("class {}", class.name));
1320 if let Some(parent) = &class.parent {
1321 out.push_str(&format!(" extends {}", parent));
1322 }
1323 if !class.interfaces.is_empty() {
1324 out.push_str(&format!(" implements {}", class.interfaces.join(", ")));
1325 }
1326 out.push_str("\n{\n");
1327 for tr in &class.traits {
1328 out.push_str(&format!("{}use {};\n", self.indent, tr));
1329 }
1330 if !class.traits.is_empty() {
1331 out.push('\n');
1332 }
1333 for (name, ty, val) in &class.constants {
1334 out.push_str(&format!(
1335 "{}const {}: {} = {};\n",
1336 self.indent, name, ty, val
1337 ));
1338 }
1339 for prop in &class.properties {
1340 out.push_str(&format!("{}{}\n", self.indent, self.emit_property(prop)));
1341 }
1342 if !class.properties.is_empty() {
1343 out.push('\n');
1344 }
1345 for method in &class.methods {
1346 out.push_str(&self.indent_block(&self.emit_function(method)));
1347 out.push('\n');
1348 }
1349 out.push_str("}\n");
1350 out
1351 }
1352 pub fn emit_script(&self, script: &PHPScript) -> std::string::String {
1354 let mut out = std::string::String::from("<?php\n");
1355 if script.strict_types {
1356 out.push_str("declare(strict_types=1);\n\n");
1357 }
1358 if let Some(ns) = &script.namespace {
1359 out.push_str(&format!("namespace {};\n\n", ns));
1360 }
1361 for (path, alias) in &script.uses {
1362 match alias {
1363 Some(a) => out.push_str(&format!("use {} as {};\n", path, a)),
1364 None => out.push_str(&format!("use {};\n", path)),
1365 }
1366 }
1367 if !script.uses.is_empty() {
1368 out.push('\n');
1369 }
1370 for iface in &script.interfaces {
1371 out.push_str(&self.emit_interface(iface));
1372 out.push('\n');
1373 }
1374 for tr in &script.traits {
1375 out.push_str(&self.emit_trait(tr));
1376 out.push('\n');
1377 }
1378 for en in &script.enums {
1379 out.push_str(&self.emit_enum(en));
1380 out.push('\n');
1381 }
1382 for class in &script.classes {
1383 out.push_str(&self.emit_class(class));
1384 out.push('\n');
1385 }
1386 for func in &script.functions {
1387 out.push_str(&self.emit_function(func));
1388 out.push('\n');
1389 }
1390 for line in &script.main {
1391 out.push_str(line);
1392 out.push('\n');
1393 }
1394 out
1395 }
1396 pub(super) fn indent_block(&self, block: &str) -> std::string::String {
1398 block
1399 .lines()
1400 .map(|line| {
1401 if line.trim().is_empty() {
1402 std::string::String::new()
1403 } else {
1404 format!("{}{}", self.indent, line)
1405 }
1406 })
1407 .collect::<Vec<_>>()
1408 .join("\n")
1409 + "\n"
1410 }
1411 pub fn emit_namespace(&self, ns: &PHPNamespace) -> std::string::String {
1413 let mut script = PHPScript::new();
1414 script.namespace = Some(ns.path.clone());
1415 script.uses = ns.uses.clone();
1416 script.functions = ns.functions.clone();
1417 script.classes = ns.classes.clone();
1418 script.interfaces = ns.interfaces.clone();
1419 script.traits = ns.traits.clone();
1420 script.enums = ns.enums.clone();
1421 self.emit_script(&script)
1422 }
1423}
1424#[allow(dead_code)]
1426#[derive(Debug, Clone)]
1427pub struct PHPExtDepGraph {
1428 pub(super) n: usize,
1429 pub(super) adj: Vec<Vec<usize>>,
1430 pub(super) rev: Vec<Vec<usize>>,
1431 pub(super) edge_count: usize,
1432}
1433impl PHPExtDepGraph {
1434 #[allow(dead_code)]
1435 pub fn new(n: usize) -> Self {
1436 Self {
1437 n,
1438 adj: vec![Vec::new(); n],
1439 rev: vec![Vec::new(); n],
1440 edge_count: 0,
1441 }
1442 }
1443 #[allow(dead_code)]
1444 pub fn add_edge(&mut self, from: usize, to: usize) {
1445 if from < self.n && to < self.n {
1446 if !self.adj[from].contains(&to) {
1447 self.adj[from].push(to);
1448 self.rev[to].push(from);
1449 self.edge_count += 1;
1450 }
1451 }
1452 }
1453 #[allow(dead_code)]
1454 pub fn succs(&self, n: usize) -> &[usize] {
1455 self.adj.get(n).map(|v| v.as_slice()).unwrap_or(&[])
1456 }
1457 #[allow(dead_code)]
1458 pub fn preds(&self, n: usize) -> &[usize] {
1459 self.rev.get(n).map(|v| v.as_slice()).unwrap_or(&[])
1460 }
1461 #[allow(dead_code)]
1462 pub fn topo_sort(&self) -> Option<Vec<usize>> {
1463 let mut deg: Vec<usize> = (0..self.n).map(|i| self.rev[i].len()).collect();
1464 let mut q: std::collections::VecDeque<usize> =
1465 (0..self.n).filter(|&i| deg[i] == 0).collect();
1466 let mut out = Vec::with_capacity(self.n);
1467 while let Some(u) = q.pop_front() {
1468 out.push(u);
1469 for &v in &self.adj[u] {
1470 deg[v] -= 1;
1471 if deg[v] == 0 {
1472 q.push_back(v);
1473 }
1474 }
1475 }
1476 if out.len() == self.n {
1477 Some(out)
1478 } else {
1479 None
1480 }
1481 }
1482 #[allow(dead_code)]
1483 pub fn has_cycle(&self) -> bool {
1484 self.topo_sort().is_none()
1485 }
1486 #[allow(dead_code)]
1487 pub fn reachable(&self, start: usize) -> Vec<usize> {
1488 let mut vis = vec![false; self.n];
1489 let mut stk = vec![start];
1490 let mut out = Vec::new();
1491 while let Some(u) = stk.pop() {
1492 if u < self.n && !vis[u] {
1493 vis[u] = true;
1494 out.push(u);
1495 for &v in &self.adj[u] {
1496 if !vis[v] {
1497 stk.push(v);
1498 }
1499 }
1500 }
1501 }
1502 out
1503 }
1504 #[allow(dead_code)]
1505 pub fn scc(&self) -> Vec<Vec<usize>> {
1506 let mut visited = vec![false; self.n];
1507 let mut order = Vec::new();
1508 for i in 0..self.n {
1509 if !visited[i] {
1510 let mut stk = vec![(i, 0usize)];
1511 while let Some((u, idx)) = stk.last_mut() {
1512 if !visited[*u] {
1513 visited[*u] = true;
1514 }
1515 if *idx < self.adj[*u].len() {
1516 let v = self.adj[*u][*idx];
1517 *idx += 1;
1518 if !visited[v] {
1519 stk.push((v, 0));
1520 }
1521 } else {
1522 order.push(*u);
1523 stk.pop();
1524 }
1525 }
1526 }
1527 }
1528 let mut comp = vec![usize::MAX; self.n];
1529 let mut components: Vec<Vec<usize>> = Vec::new();
1530 for &start in order.iter().rev() {
1531 if comp[start] == usize::MAX {
1532 let cid = components.len();
1533 let mut component = Vec::new();
1534 let mut stk = vec![start];
1535 while let Some(u) = stk.pop() {
1536 if comp[u] == usize::MAX {
1537 comp[u] = cid;
1538 component.push(u);
1539 for &v in &self.rev[u] {
1540 if comp[v] == usize::MAX {
1541 stk.push(v);
1542 }
1543 }
1544 }
1545 }
1546 components.push(component);
1547 }
1548 }
1549 components
1550 }
1551 #[allow(dead_code)]
1552 pub fn node_count(&self) -> usize {
1553 self.n
1554 }
1555 #[allow(dead_code)]
1556 pub fn edge_count(&self) -> usize {
1557 self.edge_count
1558 }
1559}
1560#[allow(dead_code)]
1561#[derive(Debug, Clone, Default)]
1562pub struct PHPPassStats {
1563 pub total_runs: u32,
1564 pub successful_runs: u32,
1565 pub total_changes: u64,
1566 pub time_ms: u64,
1567 pub iterations_used: u32,
1568}
1569impl PHPPassStats {
1570 #[allow(dead_code)]
1571 pub fn new() -> Self {
1572 Self::default()
1573 }
1574 #[allow(dead_code)]
1575 pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
1576 self.total_runs += 1;
1577 self.successful_runs += 1;
1578 self.total_changes += changes;
1579 self.time_ms += time_ms;
1580 self.iterations_used = iterations;
1581 }
1582 #[allow(dead_code)]
1583 pub fn average_changes_per_run(&self) -> f64 {
1584 if self.total_runs == 0 {
1585 return 0.0;
1586 }
1587 self.total_changes as f64 / self.total_runs as f64
1588 }
1589 #[allow(dead_code)]
1590 pub fn success_rate(&self) -> f64 {
1591 if self.total_runs == 0 {
1592 return 0.0;
1593 }
1594 self.successful_runs as f64 / self.total_runs as f64
1595 }
1596 #[allow(dead_code)]
1597 pub fn format_summary(&self) -> String {
1598 format!(
1599 "Runs: {}/{}, Changes: {}, Time: {}ms",
1600 self.successful_runs, self.total_runs, self.total_changes, self.time_ms
1601 )
1602 }
1603}
1604#[allow(dead_code)]
1606#[derive(Debug, Clone)]
1607pub struct PHPExtDomTree {
1608 pub(super) idom: Vec<Option<usize>>,
1609 pub(super) children: Vec<Vec<usize>>,
1610 pub(super) depth: Vec<usize>,
1611}
1612impl PHPExtDomTree {
1613 #[allow(dead_code)]
1614 pub fn new(n: usize) -> Self {
1615 Self {
1616 idom: vec![None; n],
1617 children: vec![Vec::new(); n],
1618 depth: vec![0; n],
1619 }
1620 }
1621 #[allow(dead_code)]
1622 pub fn set_idom(&mut self, node: usize, dom: usize) {
1623 if node < self.idom.len() {
1624 self.idom[node] = Some(dom);
1625 if dom < self.children.len() {
1626 self.children[dom].push(node);
1627 }
1628 self.depth[node] = if dom < self.depth.len() {
1629 self.depth[dom] + 1
1630 } else {
1631 1
1632 };
1633 }
1634 }
1635 #[allow(dead_code)]
1636 pub fn dominates(&self, a: usize, mut b: usize) -> bool {
1637 if a == b {
1638 return true;
1639 }
1640 let n = self.idom.len();
1641 for _ in 0..n {
1642 match self.idom.get(b).copied().flatten() {
1643 None => return false,
1644 Some(p) if p == a => return true,
1645 Some(p) if p == b => return false,
1646 Some(p) => b = p,
1647 }
1648 }
1649 false
1650 }
1651 #[allow(dead_code)]
1652 pub fn children_of(&self, n: usize) -> &[usize] {
1653 self.children.get(n).map(|v| v.as_slice()).unwrap_or(&[])
1654 }
1655 #[allow(dead_code)]
1656 pub fn depth_of(&self, n: usize) -> usize {
1657 self.depth.get(n).copied().unwrap_or(0)
1658 }
1659 #[allow(dead_code)]
1660 pub fn lca(&self, mut a: usize, mut b: usize) -> usize {
1661 let n = self.idom.len();
1662 for _ in 0..(2 * n) {
1663 if a == b {
1664 return a;
1665 }
1666 if self.depth_of(a) > self.depth_of(b) {
1667 a = self.idom.get(a).and_then(|x| *x).unwrap_or(a);
1668 } else {
1669 b = self.idom.get(b).and_then(|x| *x).unwrap_or(b);
1670 }
1671 }
1672 0
1673 }
1674}
1675#[derive(Debug, Clone, PartialEq)]
1677pub struct PHPEnum {
1678 pub name: std::string::String,
1680 pub backing_type: Option<PHPType>,
1682 pub cases: Vec<PHPEnumCase>,
1684 pub implements: Vec<std::string::String>,
1686 pub methods: Vec<PHPFunction>,
1688}
1689impl PHPEnum {
1690 pub fn new(name: impl Into<std::string::String>) -> Self {
1692 PHPEnum {
1693 name: name.into(),
1694 backing_type: None,
1695 cases: vec![],
1696 implements: vec![],
1697 methods: vec![],
1698 }
1699 }
1700 pub fn string_backed(name: impl Into<std::string::String>) -> Self {
1702 PHPEnum {
1703 name: name.into(),
1704 backing_type: Some(PHPType::String),
1705 cases: vec![],
1706 implements: vec![],
1707 methods: vec![],
1708 }
1709 }
1710}
1711#[allow(dead_code)]
1712#[derive(Debug, Clone)]
1713pub struct PHPPassConfig {
1714 pub phase: PHPPassPhase,
1715 pub enabled: bool,
1716 pub max_iterations: u32,
1717 pub debug_output: bool,
1718 pub pass_name: String,
1719}
1720impl PHPPassConfig {
1721 #[allow(dead_code)]
1722 pub fn new(name: impl Into<String>, phase: PHPPassPhase) -> Self {
1723 PHPPassConfig {
1724 phase,
1725 enabled: true,
1726 max_iterations: 10,
1727 debug_output: false,
1728 pass_name: name.into(),
1729 }
1730 }
1731 #[allow(dead_code)]
1732 pub fn disabled(mut self) -> Self {
1733 self.enabled = false;
1734 self
1735 }
1736 #[allow(dead_code)]
1737 pub fn with_debug(mut self) -> Self {
1738 self.debug_output = true;
1739 self
1740 }
1741 #[allow(dead_code)]
1742 pub fn max_iter(mut self, n: u32) -> Self {
1743 self.max_iterations = n;
1744 self
1745 }
1746}
1747#[allow(dead_code)]
1748pub struct PHPConstantFoldingHelper;
1749impl PHPConstantFoldingHelper {
1750 #[allow(dead_code)]
1751 pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
1752 a.checked_add(b)
1753 }
1754 #[allow(dead_code)]
1755 pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
1756 a.checked_sub(b)
1757 }
1758 #[allow(dead_code)]
1759 pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
1760 a.checked_mul(b)
1761 }
1762 #[allow(dead_code)]
1763 pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
1764 if b == 0 {
1765 None
1766 } else {
1767 a.checked_div(b)
1768 }
1769 }
1770 #[allow(dead_code)]
1771 pub fn fold_add_f64(a: f64, b: f64) -> f64 {
1772 a + b
1773 }
1774 #[allow(dead_code)]
1775 pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
1776 a * b
1777 }
1778 #[allow(dead_code)]
1779 pub fn fold_neg_i64(a: i64) -> Option<i64> {
1780 a.checked_neg()
1781 }
1782 #[allow(dead_code)]
1783 pub fn fold_not_bool(a: bool) -> bool {
1784 !a
1785 }
1786 #[allow(dead_code)]
1787 pub fn fold_and_bool(a: bool, b: bool) -> bool {
1788 a && b
1789 }
1790 #[allow(dead_code)]
1791 pub fn fold_or_bool(a: bool, b: bool) -> bool {
1792 a || b
1793 }
1794 #[allow(dead_code)]
1795 pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
1796 a.checked_shl(b)
1797 }
1798 #[allow(dead_code)]
1799 pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
1800 a.checked_shr(b)
1801 }
1802 #[allow(dead_code)]
1803 pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
1804 if b == 0 {
1805 None
1806 } else {
1807 Some(a % b)
1808 }
1809 }
1810 #[allow(dead_code)]
1811 pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
1812 a & b
1813 }
1814 #[allow(dead_code)]
1815 pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
1816 a | b
1817 }
1818 #[allow(dead_code)]
1819 pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
1820 a ^ b
1821 }
1822 #[allow(dead_code)]
1823 pub fn fold_bitnot_i64(a: i64) -> i64 {
1824 !a
1825 }
1826}
1827#[derive(Debug, Clone, PartialEq)]
1829pub enum PHPExpr {
1830 Lit(std::string::String),
1832 Var(std::string::String),
1834 BinOp(Box<PHPExpr>, std::string::String, Box<PHPExpr>),
1836 UnaryOp(std::string::String, Box<PHPExpr>),
1838 FuncCall(std::string::String, Vec<PHPExpr>),
1840 ArrayLit(Vec<PHPExpr>),
1842 ArrayMap(Vec<(PHPExpr, PHPExpr)>),
1844 New(std::string::String, Vec<PHPExpr>),
1846 Arrow(Box<PHPExpr>, std::string::String),
1848 NullSafe(Box<PHPExpr>, std::string::String),
1850 StaticAccess(std::string::String, std::string::String),
1852 Index(Box<PHPExpr>, Box<PHPExpr>),
1854 Ternary(Box<PHPExpr>, Box<PHPExpr>, Box<PHPExpr>),
1856 NullCoalesce(Box<PHPExpr>, Box<PHPExpr>),
1858 Closure {
1860 params: Vec<PHPParam>,
1861 use_vars: Vec<std::string::String>,
1862 return_type: Option<PHPType>,
1863 body: Vec<std::string::String>,
1864 },
1865 ArrowFn {
1867 params: Vec<PHPParam>,
1868 return_type: Option<PHPType>,
1869 body: Box<PHPExpr>,
1870 },
1871 Match {
1873 subject: Box<PHPExpr>,
1874 arms: Vec<(PHPExpr, PHPExpr)>,
1875 default: Option<Box<PHPExpr>>,
1876 },
1877 NamedArg(std::string::String, Box<PHPExpr>),
1879 Spread(Box<PHPExpr>),
1881 Cast(std::string::String, Box<PHPExpr>),
1883 Isset(Box<PHPExpr>),
1885 Empty(Box<PHPExpr>),
1887}
1888#[derive(Debug, Clone, PartialEq)]
1890pub struct PHPProperty {
1891 pub name: std::string::String,
1893 pub ty: Option<PHPType>,
1895 pub visibility: PHPVisibility,
1897 pub is_static: bool,
1899 pub readonly: bool,
1901 pub default: Option<std::string::String>,
1903}
1904impl PHPProperty {
1905 pub fn public(name: impl Into<std::string::String>, ty: Option<PHPType>) -> Self {
1907 PHPProperty {
1908 name: name.into(),
1909 ty,
1910 visibility: PHPVisibility::Public,
1911 is_static: false,
1912 readonly: false,
1913 default: None,
1914 }
1915 }
1916 pub fn private(name: impl Into<std::string::String>, ty: Option<PHPType>) -> Self {
1918 PHPProperty {
1919 name: name.into(),
1920 ty,
1921 visibility: PHPVisibility::Private,
1922 is_static: false,
1923 readonly: false,
1924 default: None,
1925 }
1926 }
1927}
1928#[derive(Debug, Clone, PartialEq)]
1930pub struct PHPNamespace {
1931 pub path: std::string::String,
1933 pub uses: Vec<(std::string::String, Option<std::string::String>)>,
1935 pub functions: Vec<PHPFunction>,
1937 pub classes: Vec<PHPClass>,
1939 pub interfaces: Vec<PHPInterface>,
1941 pub traits: Vec<PHPTrait>,
1943 pub enums: Vec<PHPEnum>,
1945}
1946impl PHPNamespace {
1947 pub fn new(path: impl Into<std::string::String>) -> Self {
1949 PHPNamespace {
1950 path: path.into(),
1951 uses: vec![],
1952 functions: vec![],
1953 classes: vec![],
1954 interfaces: vec![],
1955 traits: vec![],
1956 enums: vec![],
1957 }
1958 }
1959}
1960#[allow(dead_code)]
1961#[derive(Debug, Clone)]
1962pub struct PHPDepGraph {
1963 pub(super) nodes: Vec<u32>,
1964 pub(super) edges: Vec<(u32, u32)>,
1965}
1966impl PHPDepGraph {
1967 #[allow(dead_code)]
1968 pub fn new() -> Self {
1969 PHPDepGraph {
1970 nodes: Vec::new(),
1971 edges: Vec::new(),
1972 }
1973 }
1974 #[allow(dead_code)]
1975 pub fn add_node(&mut self, id: u32) {
1976 if !self.nodes.contains(&id) {
1977 self.nodes.push(id);
1978 }
1979 }
1980 #[allow(dead_code)]
1981 pub fn add_dep(&mut self, dep: u32, dependent: u32) {
1982 self.add_node(dep);
1983 self.add_node(dependent);
1984 self.edges.push((dep, dependent));
1985 }
1986 #[allow(dead_code)]
1987 pub fn dependents_of(&self, node: u32) -> Vec<u32> {
1988 self.edges
1989 .iter()
1990 .filter(|(d, _)| *d == node)
1991 .map(|(_, dep)| *dep)
1992 .collect()
1993 }
1994 #[allow(dead_code)]
1995 pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
1996 self.edges
1997 .iter()
1998 .filter(|(_, dep)| *dep == node)
1999 .map(|(d, _)| *d)
2000 .collect()
2001 }
2002 #[allow(dead_code)]
2003 pub fn topological_sort(&self) -> Vec<u32> {
2004 let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
2005 for &n in &self.nodes {
2006 in_degree.insert(n, 0);
2007 }
2008 for (_, dep) in &self.edges {
2009 *in_degree.entry(*dep).or_insert(0) += 1;
2010 }
2011 let mut queue: std::collections::VecDeque<u32> = self
2012 .nodes
2013 .iter()
2014 .filter(|&&n| in_degree[&n] == 0)
2015 .copied()
2016 .collect();
2017 let mut result = Vec::new();
2018 while let Some(node) = queue.pop_front() {
2019 result.push(node);
2020 for dep in self.dependents_of(node) {
2021 let cnt = in_degree.entry(dep).or_insert(0);
2022 *cnt = cnt.saturating_sub(1);
2023 if *cnt == 0 {
2024 queue.push_back(dep);
2025 }
2026 }
2027 }
2028 result
2029 }
2030 #[allow(dead_code)]
2031 pub fn has_cycle(&self) -> bool {
2032 self.topological_sort().len() < self.nodes.len()
2033 }
2034}
2035#[derive(Debug, Clone, PartialEq)]
2037pub struct PHPInterface {
2038 pub name: std::string::String,
2040 pub extends: Vec<std::string::String>,
2042 pub methods: Vec<PHPFunction>,
2044 pub constants: Vec<(std::string::String, std::string::String)>,
2046}
2047impl PHPInterface {
2048 pub fn new(name: impl Into<std::string::String>) -> Self {
2050 PHPInterface {
2051 name: name.into(),
2052 extends: vec![],
2053 methods: vec![],
2054 constants: vec![],
2055 }
2056 }
2057}