diff --git a/mjbase/src/mjbase/renamer/shadow_token.cpp b/mjbase/src/mjbase/renamer/shadow_token.cpp index e92d6aa..07e6881 100644 --- a/mjbase/src/mjbase/renamer/shadow_token.cpp +++ b/mjbase/src/mjbase/renamer/shadow_token.cpp @@ -389,6 +389,19 @@ ShadowSlotResult resolveShadowDescriptor(const ShadowAliasTable& table, const auto parts = parseShadowRef(content); switch (parts.form) { case ShadowRefForm::Raw: + // 类型段中段的 `${`(非段首):二进制名文法不含花括号, + // 未标记的引用形状混入类型名 = 调用方笔误 → fail-loud, + // 不静默替换(EscapedLiteral 显式转义另案,保留字面)。 + if (content.find('{') != std::string::npos || + content.find('}') != std::string::npos) { + result = failure(ShadowSlotError::MalformedHandle, + "internal name segment " + quoted(content) + + " in descriptor " + quoted(input) + + " contains brace characters (unmarked " + "reference mid-segment); place the handle " + "at the segment start or escape with $$"); + return false; + } out += 'L'; out += content; out += ';'; @@ -626,13 +639,41 @@ ShadowSlotResult resolveShadowJasmBody(const ShadowAliasTable& table, } else { // Raw:复合 owner.name(name 可嵌 MemberRef handle)或纯裸名。 + // 花括号裁定按段进行:段首 ${ 走句柄路径,其余位置的花括号 = + // 未标记引用中段,fail-loud(同 Descriptor 槽类型段;显示文本的 + // shadow 复合名不含 ${ 形状,零漂移不受损)。 + const auto bracesIn = [](const std::string& segment) { + return segment.find('{') != std::string::npos || + segment.find('}') != std::string::npos; + }; const auto dot = refPart.find('.'); if (dot == std::string::npos) { + if (bracesIn(refPart)) { + return failure(ShadowSlotError::MalformedHandle, + "raw reference " + quoted(refPart) + + " in jasm operand contains brace characters " + "(unmarked reference mid-segment); place the " + "handle at the segment start or escape with $$"); + } resolvedRef = refPart; // 类操作数裸名:零漂移直通 } else { const auto ownerPart = refPart.substr(0, dot); const auto namePart = refPart.substr(dot + 1); + if (ownerPart.rfind("${", 0) != 0 && bracesIn(ownerPart)) { + return failure(ShadowSlotError::MalformedHandle, + "raw owner " + quoted(ownerPart) + + " in jasm operand contains brace characters " + "(unmarked reference mid-segment); place the " + "handle at the segment start or escape with $$"); + } + if (bracesIn(namePart) && namePart.rfind("${", 0) != 0) { + return failure(ShadowSlotError::MalformedHandle, + "raw member name " + quoted(namePart) + + " in jasm operand contains brace characters " + "(unmarked reference mid-segment); place the " + "handle at the segment start or escape with $$"); + } std::string resolvedName; if (!resolveNamePart(namePart, ownerPart, resolvedName)) return result; resolvedRef = ownerPart + "." + resolvedName; diff --git a/mjbase/src/mjbase/renamer/shadow_token.hpp b/mjbase/src/mjbase/renamer/shadow_token.hpp index 3772a91..90c0af8 100644 --- a/mjbase/src/mjbase/renamer/shadow_token.hpp +++ b/mjbase/src/mjbase/renamer/shadow_token.hpp @@ -8,6 +8,12 @@ /// §3 位置三分 解析是文法驱动的:展开只发生在文法标识槽;字符串常量/注释 /// 恒等、永不解析(文法上不是标识槽,本收口根本不会被调用—— /// 该性质由测试钉住,而非运行时检查)。 +/// §4 中段裁定 `${` 出现在类型名/操作数中段(非段首)不展开、不静默: +/// 二进制名文法不含花括号,未标记的引用形状混入裸名 = +/// 调用方笔误 → Descriptor 类型段与 JasmBody 裸引用部 +/// fail-loud(malformed-handle);name 槽保持整串二选一 +/// (raw 零漂移,verifyRaw 提供核表响亮路径);`$${` 显式 +/// 转义两槽均保留字面(调用方自证字面意图)。 /// §6 错误四分类 unknown-handle / malformed-handle / raw-not-found / /// ambiguous-member,各自带固定修复动作;缺 sidecar 时任何引用 /// 形状 → 硬报错(missing-sidecar),不静默回落裸名。 diff --git a/mjbase/test/unittest/renamer/shadow_token_test.cpp b/mjbase/test/unittest/renamer/shadow_token_test.cpp index 59a5847..e2b032f 100644 --- a/mjbase/test/unittest/renamer/shadow_token_test.cpp +++ b/mjbase/test/unittest/renamer/shadow_token_test.cpp @@ -662,4 +662,124 @@ TEST(MjShadowTokenTest, JasmBodyGrammarGarbageFailsLoud) EXPECT_EQ(r.raw, ""); } +// ── §4 中段裁定(${ 不在段首:不展开、不静默)──────────────────── + +TEST(MjShadowTokenTest, MidSegmentBracesInDescriptorFailLoud) +{ + const auto table = makeTable(); + // 类型段中段的 ${(非段首):二进制名不含花括号 → malformed-handle, + // 不静默替换成含 ${ 字面的「合法描述符」。 + const auto r = resolveDesc(table, "(Lfoo${Class_1.11111111};)V"); + EXPECT_EQ(r.error, ShadowSlotError::MalformedHandle); + EXPECT_NE(std::string::npos, text(r).find("brace characters")); + + // 孤立花括号同判(同一文法违约,不只盯 ${ 二字面)。 + EXPECT_EQ(resolveDesc(table, "(Lfo{o};)V").error, ShadowSlotError::MalformedHandle); + + // $${ 显式转义保留字面(调用方自证意图,不属中段违约)。 + const auto escaped = resolveDesc(table, "(L$${a/b/Weird};)V"); + ASSERT_EQ(escaped.error, ShadowSlotError::None) << text(escaped); + EXPECT_EQ(escaped.raw, "(L${a/b/Weird};)V"); +} + +TEST(MjShadowTokenTest, NameSlotMidSegmentStaysRawZeroDrift) +{ + const auto table = makeTable(); + // name 槽整串二选一(§3):${ 不在串首 → 整串 Raw(无中段扫描), + // 解析层不展开、不判 malformed;响亮路径只来自核表位(verifyRaw): + // 默认查询核表 → raw-not-found;直通位(verifyRaw=false)恒等回显。 + const auto checked = resolve(table, "foo${Class_1.11111111}"); + EXPECT_EQ(checked.error, ShadowSlotError::RawNotFound); + + ShadowSlotQuery passthrough; + passthrough.verifyRaw = false; + const auto echoed = resolveShadowSlot(table, "foo${Class_1.11111111}", passthrough); + ASSERT_EQ(echoed.error, ShadowSlotError::None) << text(echoed); + EXPECT_EQ(echoed.raw, "foo${Class_1.11111111}"); + EXPECT_FALSE(echoed.fromHandle); +} + +TEST(MjShadowTokenTest, JasmBodyMidSegmentBracesFailLoud) +{ + const auto table = makeTable(); + // 裸引用部中段的 ${ → malformed-handle(同 Descriptor 槽类型段)。 + EXPECT_EQ(resolveJasm(table, "foo${Class_1.11111111}.fly ()V").error, + ShadowSlotError::MalformedHandle); + EXPECT_EQ(resolveJasm(table, "foo${Class_1.11111111}").error, + ShadowSlotError::MalformedHandle); + + // $${ 显式转义 → 字面直通(剥 $ 后原样回显,零漂移)。 + const auto escaped = resolveJasm(table, "$${a/b/Weird}.fly ()V"); + ASSERT_EQ(escaped.error, ShadowSlotError::None) << text(escaped); + EXPECT_EQ(escaped.raw, "${a/b/Weird}.fly ()V"); + EXPECT_FALSE(escaped.fromHandle); +} + +// ── §4 补钉:中段 guard × 合法嵌套形态矩阵 + owner 内点形状钉扎 ────── + +TEST(MjShadowTokenTest, JasmBodyCompositeOperandMatrix) +{ + const auto table = makeTable(); + const std::string cr = kClassRef; // ${Class_1.11111111} + const std::string mr = kMemberRef; // ${Class_1.11111111.m_1.22222222} + const std::string fmr = "${Class_1.11111111.f_1.33333333}"; + struct Case + { + std::string in; + bool legal; + std::string raw; + }; + const Case cases[] = { + // 合法:raw owner + 嵌套成员句柄(方法/字段 × 带/不带描述符)。 + {"a/b/Alpha." + mr + " ()V", true, "a/b/Alpha.fly ()V"}, + {"a/b/Alpha." + mr, true, "a/b/Alpha.fly"}, + {"a/b/Alpha." + fmr + " I", true, "a/b/Alpha.weight I"}, + {"a/b/Alpha." + fmr, true, "a/b/Alpha.weight"}, + // 合法:句柄 owner 复合与描述符内嵌句柄(多类型段/数组段)。 + {cr + "." + mr + " ()V", true, "a/b/Alpha.fly ()V"}, + {"a/b/Alpha.fly (L" + cr + ";L${Class_2.44444444};)V", true, + "a/b/Alpha.fly (La/b/Alpha;La/b/Beta;)V"}, + {"a/b/Alpha.weight [L" + cr + ";", true, "a/b/Alpha.weight [La/b/Alpha;"}, + // 合法:显示文本 shadow 复合名(无 ${ 形状)零漂移直通。 + {"Class_1.m_1 ()V", true, "Class_1.m_1 ()V"}, + {"Class_1.m_1", true, "Class_1.m_1"}, + // 非法:owner 中段混入花括号(调用方笔误,fail-loud)。 + {"a/b/Al${pha.fly ()V", false, ""}, + }; + for (const auto& c : cases) { + const auto r = resolveJasm(table, c.in); + if (c.legal) { + EXPECT_EQ(r.error, ShadowSlotError::None) << c.in << " -> " << text(r); + if (r.error == ShadowSlotError::None) EXPECT_EQ(r.raw, c.raw) << c.in; + } + else { + EXPECT_EQ(r.error, ShadowSlotError::MalformedHandle) << c.in << " -> " << text(r); + } + } +} + +TEST(MjShadowTokenTest, JasmBodyDottedOwnerWithNestedHandleFailsLoud) +{ + const auto table = makeTable(); + // owner 段内含 '.' 的复合名(显示形 owner + 嵌套成员句柄):JasmBody + // 槽契约的 OWNER 是裸内部名('/' 分包,不含 '.')——按首个 '.' 分段后 + // 嵌套句柄落于 name 段中段,中段 guard fail-loud;即使改在 ".${" 边界 + // 分段,dotted owner 与句柄 owner(表内内部名形)也不自洽,仍 fail-loud。 + // 两条路同为响亮报错,本测试钉住现行 guard 语义(消息带 $$ 转义指引)。 + const std::string mr = kMemberRef; + const std::string fmr = "${Class_1.11111111.f_1.33333333}"; + const std::string inputs[] = { + "a.b.Alpha." + mr + " ()V", + "a.b.Alpha." + mr, + "a.b.Alpha." + fmr + " I", + }; + for (const auto& in : inputs) { + const auto r = resolveJasm(table, in); + EXPECT_EQ(r.error, ShadowSlotError::MalformedHandle) << in << " -> " << text(r); + if (r.error == ShadowSlotError::MalformedHandle) { + EXPECT_NE(std::string::npos, text(r).find("brace characters")) << in; + } + } +} + } // namespace mjbase::renamer diff --git a/xmake.lua b/xmake.lua index 08eab28..45cc77e 100644 --- a/xmake.lua +++ b/xmake.lua @@ -98,14 +98,19 @@ LUIOX_LIBCA_MODULES = "core,crypto,str,fs,env,io,json,net,http,process,opt,threa -- 其嵌套 libca 由定义内的 add_deps 钉到上面的单源版本,故两处必须同一字面量。 includes("xmake/packages/libjbc.lua") add_requires("libca " .. LUIOX_LIBCA_VERSION, {configs = {modules = LUIOX_LIBCA_MODULES}}) -add_requires("libjbc 0.4.23") +-- libjbc 上界对齐 libjhasm 沙箱:libjhasm 0.3.3 自带内联 libjbc 定义只到 +-- 0.4.22(其自身钉定版),包沙箱按内联定义解析,顶层 0.4.23 会被合并进 +-- 沙箱需求后查无映射(version 当 ref 直取 → clone 失败)。0.4.23 为上游 +-- 仓库清理/测试收编批,消费面无功能差异;待 libjhasm 发内联含 0.4.23 的 +-- 版本后再同步上移。 +add_requires("libjbc 0.4.22") add_requires("libjstr 0.0.3") add_requires("libjzip 0.0.3") add_requires("libgarena 0.0.3") add_requires("libjclassfile 0.0.2") add_requires("libjstd 0.0.1") add_requires("libjdk_finder 0.1.0") -add_requires("libjhasm 0.3.2") +add_requires("libjhasm 0.3.3") -- 嵌套 libca 全路径钉定,缺一条就多一个实例。 add_requireconfs("libjbc.libca", {version = LUIOX_LIBCA_VERSION, configs = {modules = LUIOX_LIBCA_MODULES}}) add_requireconfs("libjstr.libca", {version = LUIOX_LIBCA_VERSION, configs = {modules = LUIOX_LIBCA_MODULES}}) @@ -116,6 +121,9 @@ add_requireconfs("libjclassfile.libjstr.libca", {version = LUIOX_LIBCA_ add_requireconfs("libjstd.libca", {version = LUIOX_LIBCA_VERSION, configs = {modules = LUIOX_LIBCA_MODULES}}) add_requireconfs("libjdk_finder.libca", {version = LUIOX_LIBCA_VERSION, configs = {modules = LUIOX_LIBCA_MODULES}}) add_requireconfs("libjhasm.libca", {version = LUIOX_LIBCA_VERSION, configs = {modules = LUIOX_LIBCA_MODULES}}) +-- libjhasm 后端分支 dep(on_load 动态加挂)显式钉定:与顶层同一 (版本, 配置) +-- 归并单实例,且防沙箱内解析漂移到内联定义外的版本。 +add_requireconfs("libjhasm.libjbc", {version = "0.4.22"}) -- i18n/resources 构建规则内嵌(.lang add_files 规则,包安装产物不带规则)。 includes("xmake/libca_rules.lua")