Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions mjbase/src/mjbase/renamer/shadow_alias.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,123 @@ ca::usize pruneShadowAliases(const mjbase::model::ResourceContainer& container,
return removed;
}

ca::usize rebindShadowRawNames(ShadowAliasTable& table,
const std::vector<RawRenameSpec>& 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<WorkEntry> work;
work.reserve(table.size());
std::unordered_map<std::string, ca::usize> classRaw;
std::unordered_map<std::string, ca::usize> memberKey;
std::unordered_map<std::string, std::vector<ca::usize>> 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,
Expand Down
30 changes: 30 additions & 0 deletions mjbase/src/mjbase/renamer/shadow_alias.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,36 @@ ca::core::Result<ca::usize, std::string> 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<RawRenameSpec>& renames);

/// @brief jasm 文本显示层 shadow 改写(#1072 显示层;输出不再是可回编译 jasm)。
///
/// 整 token 替换:class 条目按转义后内部名全 jar 范围替换(含描述符内
Expand Down
151 changes: 151 additions & 0 deletions mjbase/test/unittest/renamer/shadow_alias_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading