Skip to main content

cure_engine/
pass.rs

1//! Pass runner:fixed-point 循环 + 成本门槛 + 编辑账本。
2
3use std::collections::{BTreeMap, HashSet};
4
5use crate::kind::NodeKind;
6use crate::rule::{apply_edit, Edit, RewriteCtx, Rule};
7use crate::walk::{walk, Walk};
8use crate::lang::Lang;
9
10#[derive(Clone, Debug)]
11pub struct Config {
12    /// 最大编辑次数保险丝(成本单调下降本身已保证终止)。
13    pub max_edits: usize,
14    /// 关闭指定名字的规则。
15    pub disabled_rules: HashSet<String>,
16    /// 删除全单元零引用的 private 方法(解密器/内联后的死 helper)。
17    /// opt-in:反射(getDeclaredMethod)无法静态排除;名字匹配不分重载,
18    /// 只会更保守地保留。默认 false。
19    pub remove_dead_methods: bool,
20    /// 函数/class/对象字面量源码保真(TS/JS):打印时原样输出这些构造
21    /// 的源码文本(含 CRLF),内部不参与简化;被保真区引用的绑定不传播、
22    /// 不删除。用于 `Function.prototype.toString` 兼容;Java 后端忽略。
23    pub preserve_function_source: bool,
24}
25
26impl Default for Config {
27    fn default() -> Self {
28        Config {
29            max_edits: 100_000,
30            disabled_rules: HashSet::new(),
31            remove_dead_methods: false,
32            preserve_function_source: false,
33        }
34    }
35}
36
37impl Config {
38    pub fn enabled(&self, rule: &str) -> bool {
39        !self.disabled_rules.contains(rule)
40    }
41}
42
43#[derive(Debug, Default)]
44pub struct Report {
45    /// 成功应用的编辑总数。
46    pub edits: usize,
47    /// fixed-point 扫描轮数。
48    pub iterations: usize,
49    /// 被校验拒绝丢弃的结构提案数(这些提案需新鲜 walk 复活——外层
50    /// 收敛循环据此判断是否还有下一轮的必要)。
51    pub discarded: usize,
52    /// 按规则名统计的编辑数。
53    pub by_rule: BTreeMap<&'static str, usize>,
54}
55
56
57/// 见应用环内的注释。只处理 target==当前检查节点 的直接折叠——
58/// ancestor 目标的折叠不产生「合法→非法」转变(原形态本非语句位)。
59fn legalize_stmt_fold<L: Lang>(
60    lang: &L,
61    snap: &Walk<L>,
62    id: L::Id,
63    edit: Edit<L>,
64) -> Option<Edit<L>> {
65    let Edit::Replace { target, with } = &edit else { return Some(edit) };
66    if *target != id || lang.kind(*with) != NodeKind::Literal {
67        return Some(edit);
68    }
69    // Binary→Literal 不制造新非法性(Binary 语句本就非法输入);
70    // 只有合法语句形态(调用/new)被换成字面量才是
71    if !matches!(lang.kind(id), NodeKind::Call | NodeKind::New) {
72        return Some(edit);
73    }
74    let Some(&(parent, _)) = snap.parents.get(&id) else { return Some(edit) };
75    if lang.kind(parent) != NodeKind::ExprStmt || lang.children(parent).len() != 1 {
76        return Some(edit);
77    }
78    // 语句的容器必须是 Block:Label 独子语句被删会让 Label 无孩子
79    //(printer panic——spoon ExecutableReferencePosition 抓获);Case/
80    // Synchronized 体等容器同理保守拒绝
81    let Some(&(grand, _)) = snap.parents.get(&parent) else { return Some(edit) };
82    if lang.kind(grand) != NodeKind::Block {
83        return None; // 放弃提案(返回原 Replace 会留下裸字面量,同样非法)
84    }
85    Some(Edit::Delete { node: parent })
86}
87
88
89/// 对「根即 Replace 目标」的提案求值:返回 (新根, 规则名)。
90///
91/// 引擎 pass 对 walk **根**的 Replace 无法应用(无父槽可写——字段
92/// init 场景:`static int x = 1+2` 的 init 节点就是根,Replace
93/// 找不到 parent.set_child)。本函数让门面(拥有容器结构的一方,
94/// 如 CompilationUnit 的字段声明)拿到新根自行回写,并循环到不动点。
95///
96/// 只接受 target == root 的 Replace;成本门槛与 pass 相同(严格正)。
97pub fn fold_root<L: Lang>(
98    lang: &mut L,
99    root: L::Id,
100    rules: &[Box<dyn Rule<L>>],
101    cfg: &Config,
102) -> Option<(L::Id, &'static str)> {
103    for rule in rules {
104        if !cfg.enabled(rule.name()) {
105            continue;
106        }
107        let snap = walk(lang, root);
108        let ctx = RewriteCtx { lang: &mut *lang, walk: &snap };
109        if let Some(edit) = rule.check(ctx, root) {
110            if let Edit::Replace { target, with } = edit {
111                if target == root {
112                    let red = Edit::Replace { target, with }.cost_reduction(lang);
113                    if red > 0 {
114                        return Some((with, rule.name()));
115                    }
116                }
117            }
118        }
119    }
120    None
121}
122
123/// 对 `root`(通常是方法体 Block)运行规则集直到 fixed point。
124///
125/// 终止性:每条被应用的编辑都严格降低总成本(`Edit::cost_reduction` 校验),
126/// 成本单调下降 + 非负 ⇒ 必然终止;`max_edits` 只是防御性保险丝。
127///
128/// 同 pass 批量应用(正确性论证见 rule.rs 尾部注释):
129/// - 纯 Replace 立即应用(原位换孩子不移动兄弟下标,快照索引全程有效),
130///   并沿祖先链失效效果缓存后继续本 pass 扫描;
131/// - 含 Delete/Splice 的结构编辑记录足迹与 Splice 区间快照,pass 末按
132///   (parent, 位置) 降序校验应用,校验失配(树不再符合提案假设)→ 丢弃,
133///   下一 pass 重新提案——丢弃不降成本,单调性不受影响;
134/// - 足迹(含子树)进 touched 集:已承诺区域不再扫描、不再被新提案触碰。
135pub fn simplify<L: Lang>(
136    lang: &mut L,
137    root: L::Id,
138    rules: &[Box<dyn Rule<L>>],
139    cfg: &Config,
140) -> Report {
141    let mut report = Report::default();
142    lang.prepare(root);
143    // 类别分派桶:kind → 关注该类别的规则下标(含 kinds()==空 的通用规则)。
144    // 惰性建桶(每类别至多一次,O(规则数));Vec 按 discriminant 索引,
145    // 新变体自动扩容——没有"漏桶"风险。
146    let mut dispatch: Vec<Option<Box<[u32]>>> = vec![None; NodeKind::SLOT_COUNT];
147    loop {
148        let mut snap = walk(lang, root);
149        // touched = 立即应用的编辑足迹(真已脱离树)。位图索引=node_index
150        // (旧版 IdMap 哈希集:扫描循环每节点一次哈希查找 × 30k×136 轮)。
151        // 已排队(未应用)的编辑不改树 → 其足迹不进 touched。
152        let mut touched: Vec<u64> = Vec::new();
153        // 搬移集:立即 Replace 把既有节点挪进新位置(with 复用旧节点)。
154        // 这些节点的快照 parent 已过期——后续提案不得以其为目标(应用会
155        // 写进幽灵父=半应用)。下一 pass 新鲜 walk 后自然解除。
156        let mut moved: Vec<u64> = Vec::new();
157        struct Queued<L: Lang> {
158            /// 规则下标/提案节点:级联重查模式遗留(现按提案直接校验应用),
159            /// 保留字段供 CURE_DEBUG_PASS 追踪与未来级联复用
160            #[allow(dead_code)]
161            rule_idx: usize,
162            #[allow(dead_code)]
163            node: L::Id,
164            rule: &'static str,
165            edit: Edit<L>,
166            expects: Vec<Vec<L::Id>>,
167        }
168        let mut queued: Vec<Queued<L>> = Vec::new();
169        let mut applied = false;
170        #[allow(unused_mut)]
171        let applied_immediate = false;
172
173        'scan: for &id in &snap.ids {
174            if bit_get(&touched, lang.node_index(id)) {
175                continue;
176            }
177            // 锚点不透明:语言侧宣告该节点承载规则不可见的语义
178            //(JS var 提升陷阱——块外引用的 var 声明;可选链元素……
179            // 规则以锚点形状提案,锚点自身的不可触碰性在此统一拦截。
180            // Java 中性:Java 的不透明节点(Raw/匿名类体)本无规则触发)
181            if lang.is_opaque(id) {
182                continue;
183            }
184            let kind = lang.kind(id);
185            let ki = kind.slot();
186            debug_assert!(ki < NodeKind::SLOT_COUNT);
187            let bucket: &[u32] = {
188                let slot = &mut dispatch[ki];
189                if slot.is_none() {
190                    *slot = Some(
191                        rules
192                            .iter()
193                            .enumerate()
194                            .filter(|(_, r)| {
195                                let ks = r.kinds();
196                                ks.is_empty() || ks.contains(&kind)
197                            })
198                            .map(|(i, _)| i as u32)
199                            .collect::<Vec<u32>>()
200                            .into_boxed_slice(),
201                    );
202                }
203                slot.as_deref().unwrap()
204            };
205            for &ri in bucket {
206                let rule = &rules[ri as usize];
207                if !cfg.enabled(rule.name()) {
208                    continue;
209                }
210                let ctx = RewriteCtx {
211                    lang: &mut *lang,
212                    walk: &snap,
213                };
214                let proposal = rule.check(ctx, id);
215                let Some(edit) = proposal else {
216                    continue;
217                };
218                // JLS 14.8 合法化:Call/New 折叠为字面量且恰为
219                // ExpressionStatement 独子 → 裸字面量语句非法
220                //(`String.valueOf(temp);` → `"7";`)→ 值被丢弃的
221                // 纯调用语句整体删除(能折叠成字面量的调用必然无副作用)
222                let Some(edit) = legalize_stmt_fold(&*lang, &snap, id, edit) else {
223                    continue;
224                };
225                let structural = rule.structural();
226                let reduction = edit.cost_reduction(lang);
227                debug_assert!(
228                    structural || reduction > 0,
229                    "rule `{}` proposed a non-simplifying edit",
230                    rule.name()
231                );
232                if !structural && reduction == 0 {
233                    continue;
234                }
235                if crate::rule::is_replace_only(&edit) {
236                    // 足迹冲突:立即应用会真改树,与已应用足迹/搬移节点相交 →
237                    // 放弃本轮(下一 pass 新鲜快照重提)
238                    let mut fp = Vec::new();
239                    crate::rule::collect_footprint(&*lang, &edit, &mut fp);
240                    if fp
241                        .iter()
242                        .any(|n| bit_get(&touched, lang.node_index(*n)) || bit_get(&moved, lang.node_index(*n)))
243                    {
244                        continue;
245                    }
246                    if apply_edit(lang, &snap, &edit).is_ok() {
247                        // with 子树里的既有节点(不在快照中)已被搬移
248                        let mut stack = with_roots_of(&edit);
249                        while let Some(n) = stack.pop() {
250                            if n != snap.root && !snap.parents.contains_key(&n) {
251                                bit_set(&mut moved, lang.node_index(n));
252                            }
253                            for &c in lang.children(n) {
254                                stack.push(c);
255                            }
256                        }
257                        if std::env::var("CURE_DEBUG_PASS").is_ok() {
258                            eprintln!(
259                                "[pass] immediate {} :: {}",
260                                rule.name(),
261                                describe_edit(&*lang, &edit)
262                            );
263                        }
264                        *report.by_rule.entry(rule.name()).or_insert(0) += 1;
265                        report.edits += 1;
266                        applied = true;
267                        for n in fp {
268                            bit_set(&mut touched, lang.node_index(n));
269                        }
270                        // 祖先链效果失效:被换子树的祖先聚合效果已陈旧;
271                        // with 子树中既有节点被**搬移**——其旧容器(快照祖先)
272                        // 的事件条目同样陈旧(B2 教训:漏掉曾驱动 cff_diamond
273                        // 错误决策)。惰性重建(B3)下必须失效,否则旧条目
274                        // 永不重建。
275                        // 失效根:提案节点 + **全部**子编辑目标/with(Multi
276                        // [Replace A, Replace B] 的子目标各自需要失效——
277                        // 惰性重建下漏失效 = 陈旧事件驱动错误决策,deobfuscate
278                        // 差分当场抓获:q 声明被误删);with 含既有节点 =
279                        // 搬移,旧容器条目同样陈旧(B2 教训)
280                        let roots = {
281                            let mut r = vec![id];
282                            r.extend(edit_invalidation_roots(&edit));
283                            r
284                        };
285                        for root in roots {
286                            // 根自身先失效:规则可能**原地修改**搬移节点
287                            //(decl_assign_merge 给既有 VarDecl 加 init——
288                            // 惰性重建下条目陈旧,deobfuscate 差分 + 事件
289                            // 自检抓获:indexed 2 vs fresh 3)
290                            lang.invalidate_effect(root);
291                            let mut cur = Some(root);
292                            while let Some(a) =
293                                cur.and_then(|c| snap.parents.get(&c).map(|p| p.0))
294                            {
295                                lang.invalidate_effect(a);
296                                cur = Some(a);
297                            }
298                        }
299                        continue 'scan;
300                    }
301                } else {
302                    // 足迹冲突:与其他已排队提案相交 → 放弃(守卫对提案时树成立,
303                    // 应用时不再重验——无重叠 + 单次新鲜重建保证一致性)
304                    let mut fp = Vec::new();
305                    crate::rule::collect_footprint(&*lang, &edit, &mut fp);
306                    if fp
307                        .iter()
308                        .any(|n| bit_get(&touched, lang.node_index(*n)) || bit_get(&moved, lang.node_index(*n)))
309                    {
310                        continue;
311                    }
312                    let mut expects = Vec::new();
313                    crate::rule::collect_splice_expectations(&*lang, &edit, &mut expects);
314                    queued.push(Queued {
315                        rule_idx: ri as usize,
316                        node: id,
317                        rule: rule.name(),
318                        edit,
319                        expects,
320                    });
321                    for n in fp {
322                        bit_set(&mut touched, lang.node_index(n));
323                    }
324                    continue 'scan;
325                }
326            }
327        }
328
329        // pass 末:应用结构编辑队列。
330        // 立即路径的编辑已改树(含既有节点搬移到新位置——其快照 parent 已
331        // 过期):先重建 walk+prepare 使一切新鲜,再逐条校验应用。
332        // 校验:Replace/Delete 按节点身份搜索定位;Splice 要求被删区间仍
333        // 等于提案时记录的节点序列。失配(提案假设不再成立)→ 丢弃,
334        // 下一 pass 重新提案——丢弃不降成本,单调性不受影响。
335        // 重建条件:仅本 pass 有立即编辑(搬移过既有节点)时快照才过期;
336        // 校验是纯结构操作(孩子表查找),不需要效果/类型缓存——只建 walk。
337        if !queued.is_empty() {
338            if applied_immediate {
339                snap = walk(lang, root);
340            }
341            queued.sort_by(|a, b| {
342                let ka = crate::rule::structural_sort_key(&snap, &a.edit);
343                let kb = crate::rule::structural_sort_key(&snap, &b.edit);
344                kb.cmp(&ka)
345            });
346            for q in queued {
347                let mut expects = q.expects.as_slice();
348                let ok = crate::rule::verify_deferred(&*lang, &snap, &q.edit, &mut expects);
349                if std::env::var("CURE_DEBUG_PASS").is_ok() {
350                    eprintln!(
351                        "[pass] deferred {} {} :: {}",
352                        if ok { "apply" } else { "DISCARD" },
353                        q.rule,
354                        describe_edit(&*lang, &q.edit)
355                    );
356                }
357                if ok {
358                    // 失效集(应用**前**取——用旧快照定位祖先):目标 +
359                    // 删除 + insert/with 中的既有节点(搬移旧容器——B2 教训;
360                    // 新节点无祖先链,循环自然为零)
361                    let roots = edit_invalidation_roots(&q.edit);
362                    crate::rule::apply_deferred(&mut *lang, &snap, &q.edit);
363                    for root in roots {
364                        lang.invalidate_effect(root);
365                        let mut cur = Some(root);
366                        while let Some(a) =
367                            cur.and_then(|c| snap.parents.get(&c).map(|p| p.0))
368                        {
369                            lang.invalidate_effect(a);
370                            cur = Some(a);
371                        }
372                    }
373                    *report.by_rule.entry(q.rule).or_insert(0) += 1;
374                    report.edits += 1;
375                    applied = true;
376                } else {
377                    report.discarded += 1;
378                }
379            }
380        }
381
382        report.iterations += 1;
383        if !applied || report.edits >= cfg.max_edits {
384            break;
385        }
386        lang.prepare(root);
387    }
388    report
389}
390
391// 【教训】脏式索引重建(B2:只重建被失效的条目)经 cff_diamond 差分
392// 当场抓获失效不完备(队列编辑搬移 with 子树时,其旧容器的条目不在目标
393// 祖先链上 → 陈旧事件驱动错误决策,如误删初始化)。补目标祖先链失效
394// 仍不够——with 来源容器的失效需要跨树追踪。已放弃 B2,保留全量重建
395// + 容量保留(B1)。
396
397/// 位图置位(按需扩容)。
398fn bit_set(bits: &mut Vec<u64>, idx: usize) {
399    if idx >= bits.len() * 64 {
400        bits.resize(idx / 64 + 1, 0);
401    }
402    bits[idx / 64] |= 1u64 << (idx % 64);
403}
404
405/// 位图读位(越界 = false)。
406fn bit_get(bits: &[u64], idx: usize) -> bool {
407    bits.get(idx / 64)
408        .is_some_and(|w| (w >> (idx % 64)) & 1 == 1)
409}
410
411/// 编辑全部 with 根(Replace / 全 Replace Multi)。
412/// 编辑的全部失效根(应用**前**用旧快照定位):目标/删除节点 +
413/// insert/with 中**既有**节点(搬移——其旧容器条目陈旧,B2 教训)。
414/// 新建节点不在 snap.parents 中 → 祖先链循环自然为空,无需特判。
415/// 编辑的全部失效根(应用**前**用旧快照定位祖先链):目标/删除节点 +
416/// insert/with 中的全部节点(既有节点 = 搬移,旧容器条目陈旧——B2 教训;
417/// 新节点不在快照中,祖先链循环自然为零次,无需特判)。
418fn edit_invalidation_roots<L: Lang>(edit: &Edit<L>) -> Vec<L::Id> {
419    let mut out = Vec::new();
420    collect_all(edit, &mut out);
421    out
422}
423
424fn collect_all<L: Lang>(edit: &Edit<L>, out: &mut Vec<L::Id>) {
425    match edit {
426        Edit::Replace { target, with } => {
427            out.push(*target);
428            out.push(*with);
429        }
430        Edit::Delete { node } => out.push(*node),
431        Edit::Splice { node, insert, .. } => {
432            out.push(*node);
433            out.extend(insert.iter().copied());
434        }
435        Edit::Multi(es) => {
436            for e in es {
437                collect_all(e, out);
438            }
439        }
440    }
441}
442
443fn with_roots_of<L: Lang>(edit: &Edit<L>) -> Vec<L::Id> {
444    match edit {
445        Edit::Replace { with, .. } => vec![*with],
446        Edit::Multi(es) => es
447            .iter()
448            .filter_map(|e| match e {
449                Edit::Replace { with, .. } => Some(*with),
450                _ => None,
451            })
452            .collect(),
453        _ => Vec::new(),
454    }
455}
456
457/// 调试用:编辑的紧凑描述(节点 kind + 变体形态,不要求 L: Debug)。
458fn describe_edit<L: Lang>(lang: &L, edit: &Edit<L>) -> String {
459    fn kind_of<L: Lang>(lang: &L, id: L::Id) -> &'static str {
460        match lang.kind(id) {
461            NodeKind::Block => "Block",
462            NodeKind::VarDecl => "VarDecl",
463            NodeKind::Assign => "Assign",
464            NodeKind::ExprStmt => "ExprStmt",
465            NodeKind::Return => "Return",
466            NodeKind::If => "If",
467            NodeKind::While => "While",
468            NodeKind::VarRef => "VarRef",
469            NodeKind::Literal => "Literal",
470            NodeKind::Binary => "Binary",
471            NodeKind::Call => "Call",
472            NodeKind::Index => "Index",
473            NodeKind::Member => "Member",
474            _ => "…",
475        }
476    }
477    fn go<L: Lang>(lang: &L, e: &Edit<L>, out: &mut String) {
478        match e {
479            Edit::Replace { target, with } => {
480                out.push_str(&format!(
481                    "Replace {}→{} ",
482                    kind_of(lang, *target),
483                    kind_of(lang, *with)
484                ));
485            }
486            Edit::Delete { node } => {
487                out.push_str(&format!("Delete {} ", kind_of(lang, *node)));
488            }
489            Edit::Splice { index, remove, .. } => {
490                out.push_str(&format!("Splice @{index} -{remove} "));
491            }
492            Edit::Multi(es) => {
493                out.push('[');
494                for e in es {
495                    go(lang, e, out);
496                }
497                out.push(']');
498            }
499        }
500    }
501    let mut s = String::new();
502    go(lang, edit, &mut s);
503    s
504}