From 0f1f2de39750a74803baeed9060c6793467d61f1 Mon Sep 17 00:00:00 2001 From: Canrad <1517807724@qq.com> Date: Fri, 30 Oct 2026 10:36:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(renamer):=20shadow=20=E8=A1=A8=20rename=20?= =?UTF-8?q?=E5=90=8E=20rebind=E2=80=94=E2=80=94=E8=BA=AB=E4=BB=BD=E9=94=AE?= =?UTF-8?q?=E9=87=8D=E5=AE=9A=E4=BD=8D=20raw=20=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A5 落地:rebindShadowRawNames 原语,rename 落盘后按身份键重定位既有 条目的 raw 面。显示身份跨 rename 连续(IDA 语义):shadow 名与 token8 一律不变,重命名不改别名、不换号。 - 规格序应用:class 规格 rawName==oldName → rawName=newName,以其为 owner 的成员条目 owner 连带级联;成员规格 (owner, desc, rawName) 精确匹配 → rawName=newName(class 规格建议先于成员规格,成员规格 owner 填新内部名); - 失配容忍:未命中规格原样跳过(rename 面与表登记面允许失配,过期 残留交 pruneShadowAliases 容错——存在性口径同源); - 撞键先到者优先:类撞键 / 成员撞键 / 级联撞键整规格跳过;成员身份 与表契约同口径(kind 无关:同 (owner, raw, desc) 跨 field/method 不能共存,撞键预检含跨 kind); - 实现形态:增量索引 O(entries+specs) 无二次扫描,collect→clear→ re-add 重建(prune 同款已验证模式);sidecar 重落盘走既有 toMapping。 --- mjbase/src/mjbase/renamer/shadow_alias.cpp | 117 ++++++++++++++ mjbase/src/mjbase/renamer/shadow_alias.hpp | 30 ++++ .../unittest/renamer/shadow_alias_test.cpp | 151 ++++++++++++++++++ 3 files changed, 298 insertions(+) diff --git a/mjbase/src/mjbase/renamer/shadow_alias.cpp b/mjbase/src/mjbase/renamer/shadow_alias.cpp index 0e7b6eb..ac812e4 100644 --- a/mjbase/src/mjbase/renamer/shadow_alias.cpp +++ b/mjbase/src/mjbase/renamer/shadow_alias.cpp @@ -732,6 +732,123 @@ ca::usize pruneShadowAliases(const mjbase::model::ResourceContainer& container, return removed; } +ca::usize rebindShadowRawNames(ShadowAliasTable& table, + const std::vector& renames) +{ + // Owned 工作副本 + 三张增量索引(裸串键,一次性构建): + // - classRaw:类条目 raw → 下标(类改名定位); + // - memberKey:owner\x1fdesc\x1fraw → 下标(成员改名定位 + 撞键预检)—— + // 与表的成员唯一性同口径(kind 无关:同 (owner,raw,desc) 跨 field/method + // 视为同符号,addField/addMethod 互相拒绝),键含 kind 会让跨 kind 撞键 + // 预检漏判、重建期 add 失败丢条目; + // - byOwner:owner → 下标集(类改名的 owner 级联)。 + // 规格序应用;每次命中即改写副本并同步重键,撞键预检查的是已应用前序 + // 规格后的最新状态(先到者优先)。 + struct WorkEntry + { + ShadowKind kind; + std::string owner; + std::string desc; + std::string raw; + std::string shadow; + std::string token8; + }; + std::vector work; + work.reserve(table.size()); + std::unordered_map classRaw; + std::unordered_map memberKey; + std::unordered_map> byOwner; + const auto keyOf = [](const WorkEntry& entry) { + return entry.owner + '\x1f' + entry.desc + '\x1f' + entry.raw; + }; + for (const auto& entry : table.entries()) { + const ca::usize index = work.size(); + work.push_back({entry.kind, toStd(entry.owner), toStd(entry.desc), toStd(entry.rawName), + toStd(entry.shadowName), toStd(entry.token8)}); + if (entry.kind == ShadowKind::Class) { + classRaw.emplace(work.back().raw, index); + } + else { + memberKey.emplace(keyOf(work.back()), index); + } + byOwner[work.back().owner].push_back(index); + } + + // 成员撞键预检:目标身份 (owner, desc, raw) 是否已有条目(kind 无关)。 + const auto memberTaken = [&](const std::string& owner, const std::string& desc, + const std::string& raw) { + return memberKey.find(owner + '\x1f' + desc + '\x1f' + raw) != memberKey.end(); + }; + + ca::usize rebound = 0; + for (const auto& spec : renames) { + if (spec.newName.empty() || spec.oldName.empty()) continue; + if (spec.kind == ShadowKind::Class) { + // 类改名:类条目存在、目标 raw 未占用、且 owner 级联不产生成员 + // 撞键(新 owner 名下已有同 (kind, desc, raw) 的其它类成员)—— + // 任一不满足整规格跳过(先到者优先)。 + const auto classIt = classRaw.find(spec.oldName); + if (classIt == classRaw.end()) continue; + if (classRaw.find(spec.newName) != classRaw.end()) continue; + const auto ownerIt = byOwner.find(spec.oldName); + if (ownerIt != byOwner.end()) { + bool cascadeCollision = false; + for (const ca::usize index : ownerIt->second) { + if (work[index].kind == ShadowKind::Class) continue; + if (memberTaken(spec.newName, work[index].desc, work[index].raw)) { + cascadeCollision = true; + break; + } + } + if (cascadeCollision) continue; + } + // 应用:类条目 raw 改写 + owner 级联 + 索引重键。 + const ca::usize classIndex = classIt->second; + classRaw.erase(classIt); + classRaw.emplace(spec.newName, classIndex); + work[classIndex].raw = spec.newName; + if (ownerIt != byOwner.end()) { + for (const ca::usize index : ownerIt->second) { + if (work[index].kind == ShadowKind::Class) continue; + memberKey.erase(keyOf(work[index])); + work[index].owner = spec.newName; + memberKey.emplace(keyOf(work[index]), index); + } + } + ++rebound; + continue; + } + + // 成员改名:owner/desc/oldName 精确匹配;目标身份未占用才改写。 + const auto key = spec.owner + '\x1f' + spec.desc + '\x1f' + spec.oldName; + const auto it = memberKey.find(key); + if (it == memberKey.end()) continue; + if (memberTaken(spec.owner, spec.desc, spec.newName)) continue; + memberKey.erase(it); + work[it->second].raw = spec.newName; + memberKey.emplace(keyOf(work[it->second]), it->second); + ++rebound; + } + + if (rebound == 0) return 0; + + // 原地重建(与 prune 同规:clear() 使旧视图失效;前置撞键预检保证 + // 重加零失败,add 返回 false 属不可能状态,防御性跳过)。 + table.clear(); + for (const auto& entry : work) { + switch (entry.kind) { + case ShadowKind::Class: table.addClass(entry.raw, entry.shadow, entry.token8); break; + case ShadowKind::Field: + table.addField(entry.owner, entry.desc, entry.raw, entry.shadow, entry.token8); + break; + case ShadowKind::Method: + table.addMethod(entry.owner, entry.desc, entry.raw, entry.shadow, entry.token8); + break; + } + } + return rebound; +} + // ─────────────────────────── jasm 显示层改写 ─────────────────────────── std::string rewriteJasmForDisplay(std::string_view jasmText, const ShadowAliasTable& table, diff --git a/mjbase/src/mjbase/renamer/shadow_alias.hpp b/mjbase/src/mjbase/renamer/shadow_alias.hpp index 58c0fda..a19cb9c 100644 --- a/mjbase/src/mjbase/renamer/shadow_alias.hpp +++ b/mjbase/src/mjbase/renamer/shadow_alias.hpp @@ -238,6 +238,36 @@ ca::core::Result assignMethodShadowBatch( ca::usize pruneShadowAliases(const mjbase::model::ResourceContainer& container, ShadowAliasTable& table); +/// @brief 真名变更规格(rename 落盘后的表同步输入)。 +struct RawRenameSpec +{ + /// class 条目 = 类改名;成员条目 = 成员改名(匹配与撞键按表契约口径, + /// kind 不参与身份——同 (owner, raw, desc) 跨 field/method 不能共存)。 + ShadowKind kind = ShadowKind::Class; + std::string owner = {}; ///< 成员条目必填;class 条目留空 + std::string desc = {}; ///< 成员条目必填;class 条目留空 + std::string oldName = {}; ///< class 条目 = 旧内部名;成员条目 = 旧真名 + std::string newName = {}; ///< 新内部名 / 新真名 +}; + +/// @brief rename 后 shadow 表同步:按身份键重定位既有条目的 raw 面。 +/// +/// 语义(显示身份跨 rename 连续——shadow 名与 token8 一律不变,重命名不改 +/// 别名、不换号): +/// - 处理序 = 规格序;class 改名建议先于其成员改名(成员规格的 owner 填 +/// class 改名后的新内部名); +/// - class 规格:rawName == oldName 的类条目 → rawName = newName;以其为 +/// owner 的全部成员条目 owner 连带改写(级联); +/// - 成员规格:(owner, desc, rawName) 精确匹配 → rawName = newName; +/// - 未命中规格原样跳过(不改表不报错——rename 面与表登记面允许失配, +/// 过期残留交 pruneShadowAliases 容错); +/// - 撞键(newName 已有同身份条目):跳过该规格并计数,先到者优先; +/// - 重建全部索引(视图寿命语义不变:仍 = 本表寿命)。 +/// +/// @return 实际重绑定的条目数(含级联 owner 改写不计;撞键跳过不计)。 +ca::usize rebindShadowRawNames(ShadowAliasTable& table, + const std::vector& renames); + /// @brief jasm 文本显示层 shadow 改写(#1072 显示层;输出不再是可回编译 jasm)。 /// /// 整 token 替换:class 条目按转义后内部名全 jar 范围替换(含描述符内 diff --git a/mjbase/test/unittest/renamer/shadow_alias_test.cpp b/mjbase/test/unittest/renamer/shadow_alias_test.cpp index d5c690f..288905f 100644 --- a/mjbase/test/unittest/renamer/shadow_alias_test.cpp +++ b/mjbase/test/unittest/renamer/shadow_alias_test.cpp @@ -808,4 +808,155 @@ TEST(MjShadowAliasTableTest, batchAssignAppliesAndValidatesTransactionally) EXPECT_EQ("foo$2", toStd(table.methodShadow("A", "foo", "(Ljava/lang/String;)V"))); } +// ─────────────────── rename 后表同步(rebindShadowRawNames) ─────────────────── + +namespace { + +/// 手工表(rebind 与容器无关,直配身份面): +/// A(token aaaaaaaa):m()V=Method_1/bbbbbbbb、f I=Field_1/cccccccc; +/// B(token dddddddd):n()V=Method_2/eeeeeeee。 +ShadowAliasTable rebindTable() +{ + ShadowAliasTable table; + table.addClass("A", "Class_1", "aaaaaaaa"); + table.addMethod("A", "()V", "m", "Method_1", "bbbbbbbb"); + table.addField("A", "I", "f", "Field_1", "cccccccc"); + table.addClass("B", "Class_2", "dddddddd"); + table.addMethod("B", "()V", "n", "Method_2", "eeeeeeee"); + return table; +} + +} // namespace + +TEST(MjShadowAliasTableTest, rebindClassRenameCascadesOwnerAndKeepsIdentity) +{ + ShadowAliasTable table = rebindTable(); + const auto rebound = rebindShadowRawNames(table, {RawRenameSpec{ShadowKind::Class, {}, {}, "A", "C"}}); + EXPECT_EQ(1U, rebound); + // shadow 身份连续:新 raw 命中原 shadow/token;旧 raw 失配。 + EXPECT_EQ("Class_1", toStd(table.classShadow("C"))); + EXPECT_EQ(nullptr, table.classEntry("A")); + // owner 级联:成员条目 owner 跟随类改名,shadow/token 不变。 + EXPECT_EQ("Method_1", toStd(table.methodShadow("C", "m", "()V"))); + EXPECT_EQ("Field_1", toStd(table.fieldShadow("C", "f", "I"))); + EXPECT_TRUE(table.methodShadow("A", "m", "()V").empty()); + for (const auto& entry : table.entries()) { + if (entry.kind == ShadowKind::Class && entry.rawName == "C") + EXPECT_EQ("aaaaaaaa", toStd(entry.token8)); + if (entry.kind == ShadowKind::Method && entry.owner == "C") + EXPECT_EQ("bbbbbbbb", toStd(entry.token8)); + } + // 同表其它类(B)不受牵连。 + EXPECT_EQ("Class_2", toStd(table.classShadow("B"))); + EXPECT_EQ("Method_2", toStd(table.methodShadow("B", "n", "()V"))); +} + +TEST(MjShadowAliasTableTest, rebindMemberRenameMatchesExactIdentity) +{ + ShadowAliasTable table = rebindTable(); + const auto rebound = + rebindShadowRawNames(table, {RawRenameSpec{ShadowKind::Method, "A", "()V", "m", "renamed"}}); + EXPECT_EQ(1U, rebound); + EXPECT_EQ("Method_1", toStd(table.methodShadow("A", "renamed", "()V"))); + EXPECT_TRUE(table.methodShadow("A", "m", "()V").empty()); + // desc 参与身份:同名字面异 desc 的规格不命中(无重建、表零变化)。 + ShadowAliasTable untouched = rebindTable(); + EXPECT_EQ(0U, rebindShadowRawNames( + untouched, {RawRenameSpec{ShadowKind::Method, "A", "(J)V", "m", "renamed"}})); + EXPECT_EQ("Method_1", toStd(untouched.methodShadow("A", "m", "()V"))); +} + +TEST(MjShadowAliasTableTest, rebindUnknownSpecsAreNoOp) +{ + ShadowAliasTable table = rebindTable(); + const auto size = table.size(); + EXPECT_EQ(0U, rebindShadowRawNames(table, + {RawRenameSpec{ShadowKind::Class, {}, {}, "X", "Y"}, + RawRenameSpec{ShadowKind::Method, "A", "()V", "ghost", "any"}, + RawRenameSpec{ShadowKind::Class, {}, {}, "A", ""}})); + EXPECT_EQ(size, table.size()); + EXPECT_EQ("Class_1", toStd(table.classShadow("A"))); +} + +TEST(MjShadowAliasTableTest, rebindCollisionsSkipSpecFirstWins) +{ + // 跨 owner 同名不撞(身份含 owner):B.n()V → m 正常落地。 + ShadowAliasTable crossOwner = rebindTable(); + EXPECT_EQ(1U, rebindShadowRawNames(crossOwner, + {RawRenameSpec{ShadowKind::Method, "B", "()V", "n", "m"}})); + EXPECT_EQ("Method_2", toStd(crossOwner.methodShadow("B", "m", "()V"))); + // 同 owner 撞键:目标身份已有条目 → 规格跳过(先到者优先)。 + ShadowAliasTable table = rebindTable(); + table.addMethod("A", "()V", "m2", "Method_3", "ffffffff"); + EXPECT_EQ(0U, rebindShadowRawNames(table, + {RawRenameSpec{ShadowKind::Method, "A", "()V", "m2", "m"}})); + EXPECT_EQ("Method_1", toStd(table.methodShadow("A", "m", "()V"))); + EXPECT_EQ("Method_3", toStd(table.methodShadow("A", "m2", "()V"))); + // 类撞键:A → B(B 已登记)→ 跳过。 + EXPECT_EQ(0U, rebindShadowRawNames(table, {RawRenameSpec{ShadowKind::Class, {}, {}, "A", "B"}})); + EXPECT_EQ("Class_1", toStd(table.classShadow("A"))); +} + +TEST(MjShadowAliasTableTest, rebindClassCascadeCollisionSkipsWholeSpec) +{ + // 级联撞键:A 的成员迁往 C 后与 C 既有成员 (desc, raw) 相同 → 整规格 + // 跳过(类与成员都不动)。 + ShadowAliasTable table = rebindTable(); + table.addClass("C", "Class_3", "66666666"); + table.addMethod("C", "()V", "m", "Method_9", "77777777"); + EXPECT_EQ(0U, rebindShadowRawNames(table, {RawRenameSpec{ShadowKind::Class, {}, {}, "A", "C"}})); + EXPECT_EQ("Class_1", toStd(table.classShadow("A"))); + EXPECT_EQ("Method_1", toStd(table.methodShadow("A", "m", "()V"))); + EXPECT_EQ("Method_9", toStd(table.methodShadow("C", "m", "()V"))); +} + +TEST(MjShadowAliasTableTest, rebindSpecOrderClassBeforeMembers) +{ + // 契约序:class 改名先于成员改名(成员规格 owner 填新名)。 + ShadowAliasTable table = rebindTable(); + const auto rebound = rebindShadowRawNames( + table, + {RawRenameSpec{ShadowKind::Class, {}, {}, "A", "C"}, + RawRenameSpec{ShadowKind::Method, "C", "()V", "m", "fly"}}); + EXPECT_EQ(2U, rebound); + EXPECT_EQ("Method_1", toStd(table.methodShadow("C", "fly", "()V"))); +} + +TEST(MjShadowAliasTableTest, rebindFieldRenameKeepsTokenAndMappingRoundtrip) +{ + ShadowAliasTable table = rebindTable(); + // field 规格:改名后 shadow/token 原样(显示身份连续)。 + const auto rebound = rebindShadowRawNames( + table, {RawRenameSpec{ShadowKind::Field, "A", "I", "f", "renamedF"}}); + EXPECT_EQ(1U, rebound); + EXPECT_EQ("Field_1", toStd(table.fieldShadow("A", "renamedF", "I"))); + EXPECT_TRUE(table.fieldShadow("A", "f", "I").empty()); + for (const auto& entry : table.entries()) { + if (entry.kind == ShadowKind::Field && entry.rawName == "renamedF") + EXPECT_EQ("cccccccc", toStd(entry.token8)); + } + + // sidecar 重落盘通路(toMapping 枢纽):往返后新 raw 面保真。 + const auto back = ShadowAliasTable::fromMapping(table.toMapping()); + EXPECT_EQ("Field_1", toStd(back.fieldShadow("A", "renamedF", "I"))); + EXPECT_EQ("Class_1", toStd(back.classShadow("A"))); + EXPECT_EQ("Method_1", toStd(back.methodShadow("A", "m", "()V"))); +} + +TEST(MjShadowAliasTableTest, rebindCrossKindCollisionSkipsAndKeepsEntry) +{ + // 跨 kind 撞键按表契约同口径(身份 kind 无关):field 改名目标与既有 + // method 的 (owner, desc, raw) 相同 → 整规格跳过,绝不静默丢条目。 + ShadowAliasTable table; + table.addClass("A", "Class_1", "aaaaaaaa"); + table.addMethod("A", "I", "dup", "Method_1", "bbbbbbbb"); // method 占位 (A,I,dup) + table.addField("A", "I", "other", "Field_1", "cccccccc"); + const auto size = table.size(); + EXPECT_EQ(0U, rebindShadowRawNames( + table, {RawRenameSpec{ShadowKind::Field, "A", "I", "other", "dup"}})); + EXPECT_EQ(size, table.size()); + EXPECT_EQ("Method_1", toStd(table.methodShadow("A", "dup", "I"))); + EXPECT_EQ("Field_1", toStd(table.fieldShadow("A", "other", "I"))); +} + } // namespace mjbase::renamer