From dd0636e1190e55d0bd90e89edcfbade5a56c4062 Mon Sep 17 00:00:00 2001 From: Canrad <1517807724@qq.com> Date: Thu, 29 Oct 2026 20:13:08 +0800 Subject: [PATCH] =?UTF-8?q?refactor(workspace):=20workspace=20=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E6=95=B4=E4=BD=93=E9=80=80=E5=BD=B9=EF=BC=8C=E7=A7=BB?= =?UTF-8?q?=E4=BA=A4=E4=B8=8B=E6=B8=B8=20mjt=20=E8=87=AA=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 裁决:workspace 概念不留在基座——WorkspaceBase 单 class 66 个虚方法把 可编辑容器形态绑死在 class/JAR 视图,model::ResourceContainer 私有封死, dex/apk 等新容器形态无插入位;「资源容器 + 分析项目」的分层属下游消费 方自理。原「两面重组」挂账作废,dev_plan 改记退役申报。 - 删 src/mjbase/workspace/(12 文件)与 test/unittest/workspace/(5 文件) - xmake 摘除对应 src/test glob,顺手清掉 resolved_call_graph 迁移后 的 stale 精确路径行(#101 遗留,仅 warning) - 基座内零消费方(无 include 实证);下游 mjt 已先行收编引擎 (ns mjt::workspace),随迁测试 40 例绿 - 套件 1933 用例 1930 绿 + 3 既有 POSIX 跳过(1973 − 40 随迁) --- mjbase/dev_plan.md | 23 +- .../workspace/class_mutation_service.cpp | 1551 ------ .../workspace/class_mutation_service.hpp | 270 - .../mjbase/workspace/classfile_support.cpp | 308 -- .../mjbase/workspace/classfile_support.hpp | 223 - mjbase/src/mjbase/workspace/i_workspace.hpp | 304 -- .../src/mjbase/workspace/reference_scan.cpp | 250 - .../src/mjbase/workspace/reference_scan.hpp | 128 - mjbase/src/mjbase/workspace/summary_scan.cpp | 32 - mjbase/src/mjbase/workspace/summary_scan.hpp | 25 - mjbase/src/mjbase/workspace/workspace.cpp | 4806 ----------------- mjbase/src/mjbase/workspace/workspace.hpp | 461 -- .../src/mjbase/workspace/workspace_types.hpp | 693 --- .../workspace/class_mutation_access_test.cpp | 225 - .../workspace/workspace_ledger_test.cpp | 376 -- .../workspace_supporting_role_test.cpp | 147 - .../unittest/workspace/workspace_test.cpp | 1879 ------- .../workspace_validation_gate_test.cpp | 315 -- mjbase/xmake.lua | 11 +- 19 files changed, 13 insertions(+), 12014 deletions(-) delete mode 100644 mjbase/src/mjbase/workspace/class_mutation_service.cpp delete mode 100644 mjbase/src/mjbase/workspace/class_mutation_service.hpp delete mode 100644 mjbase/src/mjbase/workspace/classfile_support.cpp delete mode 100644 mjbase/src/mjbase/workspace/classfile_support.hpp delete mode 100644 mjbase/src/mjbase/workspace/i_workspace.hpp delete mode 100644 mjbase/src/mjbase/workspace/reference_scan.cpp delete mode 100644 mjbase/src/mjbase/workspace/reference_scan.hpp delete mode 100644 mjbase/src/mjbase/workspace/summary_scan.cpp delete mode 100644 mjbase/src/mjbase/workspace/summary_scan.hpp delete mode 100644 mjbase/src/mjbase/workspace/workspace.cpp delete mode 100644 mjbase/src/mjbase/workspace/workspace.hpp delete mode 100644 mjbase/src/mjbase/workspace/workspace_types.hpp delete mode 100644 mjbase/test/unittest/workspace/class_mutation_access_test.cpp delete mode 100644 mjbase/test/unittest/workspace/workspace_ledger_test.cpp delete mode 100644 mjbase/test/unittest/workspace/workspace_supporting_role_test.cpp delete mode 100644 mjbase/test/unittest/workspace/workspace_test.cpp delete mode 100644 mjbase/test/unittest/workspace/workspace_validation_gate_test.cpp diff --git a/mjbase/dev_plan.md b/mjbase/dev_plan.md index b12a084..79a6e00 100644 --- a/mjbase/dev_plan.md +++ b/mjbase/dev_plan.md @@ -893,20 +893,15 @@ mjt-deobf 侧尚在 mbase 栈消费的 opt pass 逐件裁决;"结构性吸收" ClassBytesBuilder 标签装配),pop2/swap 落点深度与返回栈深不再依赖 汇编器宽放——不可验证栈形(如裸 `装载;pop2`)不再作为夹具。stack 套 件 19 绿,全量 1973 用例 1970 绿(+3 既有跳过)。 -- [ ] workspace 重组(资源容器 + 分析项目两面对):WorkspaceBase 单 - class 66 个虚方法绑死四族职责——资源装载(openJar/openClass/supporting - 容器角色挂载)、只读分析查询(search ×5/xref ×3/层次/字节码/摘要/jasm)、 - 编辑台账(undo/redo/savepoint/ledger 落盘/导出)、高级操作(remap/ - rename/pipeline/workflow/repair);容器本体 model::ResourceContainer 被 - 私有成员封死,消费方无法自带容器实现、无法自定义分析组合,dex/apk 新 - 容器形态无插入位。目标形:**资源容器服务**(装载/角色挂载/资源读写/ - 字节级编辑台账/导出,验证门外置为可插拔)+ **分析项目会话**(外部挂到 - 容器上、不拥有它,消费方自定义绑定哪些分析面:层次/引用图/搜索索引/ - 管线),既有 project::AnalysisProject 是持久文档层(工程 JSON)继续 - 充当持久半边。mjt 消费面实测 66 虚方法全量触达(revision 68 处、 - classInfo 24 处、undo 20 处、flushLedger 9 处…,JarWorkspace 继承 - WorkspaceBase),迁移必须 facade 过渡零破坏。dex/apk = 只新增容器适 - 配器,分析面按容器能力协商,不再动 workspace 单体。 +- [x] workspace 退役(重组方案作废,改整体移交):原重组挂账(资源容器 + 服务 + 分析项目会话两面)评审否决——裁决是 workspace 概念不留在基座: + WorkspaceBase 单 class 66 个虚方法把容器形态绑死在 class/JAR 视图, + model::ResourceContainer 被私有成员封死,dex/apk 容器形态无插入位; + 「资源容器 + 分析项目」的正确分层属下游消费方自理,基座不预设。处置: + 引擎 12 源文件 + 5 测试文件整体移交 mjt(ns mjbase::workspace → + mjt::workspace,兄弟域经别名保持正文零改动),本仓模块目录、测试 + 目录与构建 glob 全量删除;基座内零消费方(实测无 include),下游 + mjt 侧 66 虚方法消费面随迁不受影响。 - [x] spi⇄pass 解环:环只在 spi/pass_name_registry.cpp 与 spi/pass_registry.cpp 两 .cpp 的组合根(种子段 52 个 pass 头 + 内置 管线段 11 个 pass 头)。整段迁 pass/default_pass_registry.cpp 与 diff --git a/mjbase/src/mjbase/workspace/class_mutation_service.cpp b/mjbase/src/mjbase/workspace/class_mutation_service.cpp deleted file mode 100644 index db7b9d4..0000000 --- a/mjbase/src/mjbase/workspace/class_mutation_service.cpp +++ /dev/null @@ -1,1551 +0,0 @@ -#include "mjbase/workspace/class_mutation_service.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "jbc/analysis/frame.hpp" -#include "jbc/core/insn_codec.hpp" -#include "jbc/core/opcode.hpp" -#include "jbc/core/writer.hpp" -#include "jbc/ir/error.hpp" -#include "jbc/ir/units.hpp" - -#include "mjbase/jasm/jvm_jasm_service.hpp" -#include "mjbase/jasm/resource_jasm_service.hpp" -#include "mjbase/model/resource_model.hpp" -#include "mjbase/spi/class_resource_pending_commit.hpp" -#include "mjbase/workspace/classfile_support.hpp" - -namespace mjbase::workspace { -namespace { - -using jbc::core::CodeAttr; -using jbc::core::Op; -using jbc::core::RawInsn; -using jbc::core::RawAttr; -using jbc::ir::ClassSession; -using jbc::ir::CodeUnit; -using jbc::ir::Insn; -using jbc::ir::InsnId; -using jbc::ir::LabelId; -using jbc::ir::kNoInsn; -using support::utf8Text; - -/// @brief NEWARRAY 元素类型码(JVMS 6.5 atable;jbc 未提供具名常量)。 -constexpr ca::i32 kAtypeBoolean = 4; -constexpr ca::i32 kAtypeLong = 11; - -WorkspaceErrorInfo error(WorkspaceError code, const std::string& operation, - const std::string& detail) -{ - return WorkspaceErrorInfo{code, operation + ": " + detail}; -} - -jbc::core::MemberInfo* findMethod(jbc::core::ClassFile& cf, const MemberSelector& selector) -{ - for (auto& method : cf.methods) { - if (utf8Text(cf, method.nameCp) == selector.name && - utf8Text(cf, method.descCp) == selector.descriptor) - return &method; - } - return nullptr; -} - -jbc::core::MemberInfo* findField(jbc::core::ClassFile& cf, const MemberSelector& selector) -{ - for (auto& field : cf.fields) { - if (utf8Text(cf, field.nameCp) == selector.name && - utf8Text(cf, field.descCp) == selector.descriptor) - return &field; - } - return nullptr; -} - -// ── 访问位校验 ──────────────────────────────────────────────── - -WorkspaceErrorInfo validateVisibility(ca::i32 access, const std::string& operation) -{ - const ca::i32 visibility = access & (kAccPublic | kAccPrivate | kAccProtected); - if (visibility != 0 && visibility != kAccPublic && visibility != kAccPrivate && - visibility != kAccProtected) - return error(WorkspaceError::InvalidArgument, operation, - "public, private and protected are mutually exclusive"); - return WorkspaceErrorInfo{}; -} - -WorkspaceErrorInfo validateAccess(ca::i32 access, AnnotationTarget target, - const std::string& operation) -{ - WorkspaceErrorInfo status = validateVisibility(access, operation); - if (status.is_err()) return status; - ca::i32 allowed = 0; - switch (target) { - case AnnotationTarget::Class: - allowed = kAccPublic | kAccFinal | kAccSuper | kAccInterface | kAccAbstract | - kAccSynthetic | kAccAnnotation | kAccEnum | kAccModule | kAccRecord | - kAccDeprecated | kAccSealed; - if ((access & kAccFinal) && (access & kAccAbstract)) - return error(WorkspaceError::InvalidArgument, operation, - "class cannot be final and abstract"); - if ((access & kAccAnnotation) && !(access & kAccInterface)) - return error(WorkspaceError::InvalidArgument, operation, - "annotation class must be an interface"); - if ((access & kAccInterface) && !(access & kAccAbstract)) - return error(WorkspaceError::InvalidArgument, operation, - "interface must be abstract"); - break; - case AnnotationTarget::Field: - allowed = kAccPublic | kAccPrivate | kAccProtected | kAccStatic | kAccFinal | - kAccVolatile | kAccTransient | kAccSynthetic | kAccEnum | kAccDeprecated; - if ((access & kAccFinal) && (access & kAccVolatile)) - return error(WorkspaceError::InvalidArgument, operation, - "field cannot be final and volatile"); - break; - case AnnotationTarget::Method: - allowed = kAccPublic | kAccPrivate | kAccProtected | kAccStatic | kAccFinal | - kAccSynchronized | kAccBridge | kAccVarargs | kAccNative | kAccAbstract | - kAccStrict | kAccSynthetic | kAccDeprecated; - if ((access & kAccAbstract) && - (access & (kAccPrivate | kAccStatic | kAccFinal | kAccSynchronized | kAccNative | - kAccStrict))) - return error(WorkspaceError::InvalidArgument, operation, - "abstract method has an incompatible access flag"); - break; - } - if ((access & ~allowed) != 0) - return error(WorkspaceError::InvalidArgument, operation, - "access contains invalid flag bits"); - return WorkspaceErrorInfo{}; -} - -// ── 访问位增量编辑(change_access)───────────────────────────────── - -/// @brief "deprecated" 名单项的归属:Deprecated 属性(非 access flag)。 -constexpr const char* kDeprecatedFlagName = "deprecated"; - -/// @brief Deprecated 属性增删(0x20000 是类属性不是 access flag;映射到 -/// 属性编辑,禁止静默丢弃)。零长度属性即 JVMS 的 Deprecated 形态。 -void setDeprecatedAttribute(jbc::core::ClassFile& cf, bool present) -{ - jbc::core::AttrSlot* existing = support::findAttr(cf, cf.attrs, "Deprecated"); - if (present && existing == nullptr) { - cf.attrs.push_back(jbc::core::AttrSlot::makeNew( - support::internUtf8(cf, "Deprecated"), - std::make_unique(std::vector{}))); - } - else if (!present && existing != nullptr) { - for (auto it = cf.attrs.begin(); it != cf.attrs.end(); ++it) { - if (cf.cp.utf8Equals(it->nameCp, "Deprecated")) { - cf.attrs.erase(it); - return; - } - } - } -} - -/// @brief flag 名单 → 位掩码(change_access 增量语义的解析面)。 -/// -/// 未知名报错并列出 class 目标合法 flag 全集;"deprecated" 单列(Deprecated -/// 属性,非 access flag——错误提示一并注明)。 -WorkspaceErrorInfo parseClassAccessFlagList(const std::vector& names, - const std::string& group, - ca::u32& mask, - bool& deprecated) -{ - static const std::pair kClassFlags[] = { - {"public", kAccPublic}, {"final", kAccFinal}, - {"super", kAccSuper}, {"interface", kAccInterface}, - {"abstract", kAccAbstract}, {"synthetic", kAccSynthetic}, - {"annotation", kAccAnnotation}, {"enum", kAccEnum}, - {"module", kAccModule}, {"record", kAccRecord}, - {"sealed", kAccSealed}, - }; - mask = 0; - deprecated = false; - for (const std::string& name : names) { - if (name == kDeprecatedFlagName) { - deprecated = true; - continue; - } - bool known = false; - for (const auto& entry : kClassFlags) { - if (name == entry.first) { - mask |= entry.second; - known = true; - break; - } - } - if (known) continue; - std::string legal; - for (const auto& entry : kClassFlags) { - if (!legal.empty()) legal += ", "; - legal += entry.first; - } - return error(WorkspaceError::InvalidArgument, "change class access", - "unknown access flag '" + group + ": " + name + - "' (legal flags: " + legal + "; '" + kDeprecatedFlagName + - "' is a class attribute, not an access flag)"); - } - return WorkspaceErrorInfo{}; -} - -// ── 常量物化 ───────────────────────────────────────────────────────────── -// -// libjbc 常量池只追加面覆盖 Utf8/String/Class/Integer/Long/Float/Double 与 -// 成员引用(经会话 intern 去重);MethodHandle/MethodType/Dynamic/ -// InvokeDynamic 无追加 API(主线缺口)——对应请求在入口拒绝。 - -WorkspaceErrorInfo validateHandle(const HandleSpec& spec) -{ - if (spec.tag < 1 || spec.tag > 9 || spec.owner.empty() || spec.name.empty() || - spec.descriptor.empty()) { - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "handle requires tag 1..9, owner, name and descriptor"}; - } - return WorkspaceErrorInfo{}; -} - -/// @brief 校验常量规格可物化(Handle/Dynamic 因 libjbc CP 追加缺口被拒)。 -WorkspaceErrorInfo validateConstant(const ConstantSpec& spec, ca::usize depth = 0) -{ - if (depth > 8) - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "constant dynamic nesting exceeds 8"}; - switch (spec.kind) { - case ConstantKind::String: - case ConstantKind::Integer: - case ConstantKind::Long: - case ConstantKind::Float: - case ConstantKind::Double: - return WorkspaceErrorInfo{}; - case ConstantKind::Type: - if (spec.descriptor.empty()) - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "type constant descriptor is required"}; - return WorkspaceErrorInfo{}; - case ConstantKind::Handle: - return validateHandle(spec.handle); - case ConstantKind::Dynamic: - { - if (spec.name.empty() || spec.descriptor.empty()) - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "constant dynamic name and descriptor are required"}; - if (spec.bootstrapArguments.size() > 128) - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "bootstrap argument count exceeds 128"}; - WorkspaceErrorInfo status = validateHandle(spec.bootstrapMethod); - if (status.is_err()) return status; - for (const ConstantSpec& argument : spec.bootstrapArguments) { - status = validateConstant(argument, depth + 1); - if (status.is_err()) return status; - } - return WorkspaceErrorInfo{}; - } - } - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, "unsupported constant kind"}; -} - -/// @brief 物化常量为 CP 索引;前置条件 validateConstant 已通过。 -ca::u32 makeConstantCp(ClassSession& session, const ConstantSpec& spec) -{ - jbc::core::ClassFile& cf = session.backing(); - switch (spec.kind) { - case ConstantKind::String: return session.internString(spec.stringValue); - case ConstantKind::Integer: return session.internInteger(spec.intValue); - case ConstantKind::Long: return cf.cp.addLong(spec.longValue); - case ConstantKind::Float: - { - ca::u32 bits = 0; - std::memcpy(&bits, &spec.floatValue, sizeof(bits)); - return cf.cp.addFloatBits(bits); - } - case ConstantKind::Double: - { - ca::u64 bits = 0; - std::memcpy(&bits, &spec.doubleValue, sizeof(bits)); - return cf.cp.addDoubleBits(bits); - } - case ConstantKind::Type: return session.internClass(spec.descriptor); - default: return 0; // Handle/Dynamic 在 validateConstant 已拒绝 - } -} - -/// @brief 字段常量:种类须匹配描述符(照旧 fieldConstant 校验)。 -WorkspaceErrorInfo fieldConstant(const ClassMutation& mutation) -{ - if (!mutation.hasFieldValue) return WorkspaceErrorInfo{}; - const std::string& descriptor = mutation.member.descriptor; - const ConstantKind kind = mutation.fieldValue.kind; - const bool valid = - ((descriptor == "Z" || descriptor == "B" || descriptor == "C" || descriptor == "S" || - descriptor == "I") && - kind == ConstantKind::Integer) || - (descriptor == "J" && kind == ConstantKind::Long) || - (descriptor == "F" && kind == ConstantKind::Float) || - (descriptor == "D" && kind == ConstantKind::Double) || - (descriptor == "Ljava/lang/String;" && kind == ConstantKind::String); - if (!valid) - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "field constant kind does not match its descriptor"}; - return validateConstant(mutation.fieldValue); -} - -// ── 指令形状校验与物化(照旧 validInstructionShape/makeInstruction)────── - -bool isSimpleOpcode(ca::i32 opcode) -{ - using O = Op; - const auto at = [](O op) { return static_cast(op); }; - return (opcode >= at(O::NOP) && opcode <= at(O::DCONST_1)) || - (opcode >= at(O::IALOAD) && opcode <= at(O::SALOAD)) || - (opcode >= at(O::IASTORE) && opcode <= at(O::SASTORE)) || - (opcode >= at(O::POP) && opcode <= at(O::SWAP)) || - (opcode >= at(O::IADD) && opcode <= at(O::LXOR)) || - (opcode >= at(O::I2L) && opcode <= at(O::I2S)) || - (opcode >= at(O::LCMP) && opcode <= at(O::DCMPG)) || - (opcode >= at(O::IRETURN) && opcode <= at(O::RETURN)) || - opcode == at(O::ARRAYLENGTH) || opcode == at(O::ATHROW) || - opcode == at(O::MONITORENTER) || opcode == at(O::MONITOREXIT); -} - -/// @brief 指令形状校验;detail 输出 "unknown label: " 同文本。 -bool validInstructionShape(const InstructionSpec& spec, std::string& detail) -{ - using O = Op; - const auto at = [](O op) { return static_cast(op); }; - switch (spec.kind) { - case InstructionKind::Simple: - if (!isSimpleOpcode(spec.opcode)) detail = "opcode is not a no-operand JVM instruction"; - break; - case InstructionKind::Int: - if (spec.opcode != at(O::BIPUSH) && spec.opcode != at(O::SIPUSH) && - spec.opcode != at(O::NEWARRAY)) - detail = "opcode is not an int-operand JVM instruction"; - else if (spec.opcode == at(O::BIPUSH) && (spec.operand < -128 || spec.operand > 127)) - detail = "bipush operand is outside -128..127"; - else if (spec.opcode == at(O::SIPUSH) && - (spec.operand < -32768 || spec.operand > 32767)) - detail = "sipush operand is outside -32768..32767"; - else if (spec.opcode == at(O::NEWARRAY) && - (spec.operand < kAtypeBoolean || spec.operand > kAtypeLong)) - detail = "newarray operand is outside the JVM primitive type range"; - break; - case InstructionKind::Var: - if (!((spec.opcode >= at(O::ILOAD) && spec.opcode <= at(O::ALOAD)) || - (spec.opcode >= at(O::ISTORE) && spec.opcode <= at(O::ASTORE)) || - spec.opcode == at(O::RET))) - detail = "opcode is not a local-variable JVM instruction"; - else if (spec.operand < 0 || spec.operand > 65535) - detail = "local variable slot is outside 0..65535"; - break; - case InstructionKind::Type: - if (spec.opcode != at(O::NEW) && spec.opcode != at(O::ANEWARRAY) && - spec.opcode != at(O::CHECKCAST) && spec.opcode != at(O::INSTANCEOF)) - detail = "opcode is not a type JVM instruction"; - else if (spec.descriptor.empty()) - detail = "type instruction descriptor is required"; - break; - case InstructionKind::Field: - if (spec.opcode < at(O::GETSTATIC) || spec.opcode > at(O::PUTFIELD)) - detail = "opcode is not a field JVM instruction"; - else if (spec.owner.empty() || spec.name.empty() || spec.descriptor.empty()) - detail = "field instruction requires owner, name and descriptor"; - break; - case InstructionKind::Method: - if (spec.opcode < at(O::INVOKEVIRTUAL) || spec.opcode > at(O::INVOKEINTERFACE)) - detail = "opcode is not a method JVM instruction"; - else if (spec.owner.empty() || spec.name.empty() || spec.descriptor.empty()) - detail = "method instruction requires owner, name and descriptor"; - break; - case InstructionKind::Jump: - if (!((spec.opcode >= at(O::IFEQ) && spec.opcode <= at(O::JSR)) || - spec.opcode == at(O::IFNULL) || spec.opcode == at(O::IFNONNULL))) - detail = "opcode is not a jump JVM instruction"; - break; - case InstructionKind::Iinc: - if (spec.operand < 0 || spec.operand > 65535 || spec.increment < -32768 || - spec.increment > 32767) - detail = "iinc slot or increment is outside the JVM wide range"; - break; - default: break; - } - return detail.empty(); -} - -/// @brief 物化一条指令为 jbc Insn(CP 操作数经会话 intern;跳转/switch 的目标 -/// 句柄经 @p outTarget 交付——此时 Insn 尚未进入 DOM)。 -/// @param labels 符号标签 → DOM 句柄;Jump/switch 形状必查。 -WorkspaceErrorInfo makeInstruction(ClassSession& session, const InstructionSpec& spec, - const std::unordered_map& labels, - Insn& out, LabelId& outTarget) -{ - std::string detail; - if (!validInstructionShape(spec, detail)) - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, detail}; - jbc::core::ClassFile& cf = session.backing(); - out = Insn{}; - out.op = static_cast(spec.opcode); - switch (spec.kind) { - case InstructionKind::Simple: return WorkspaceErrorInfo{}; - case InstructionKind::Int: out.a = spec.operand; return WorkspaceErrorInfo{}; - case InstructionKind::Var: - out.a = spec.operand; - out.wide = spec.operand > 255; - return WorkspaceErrorInfo{}; - case InstructionKind::Type: - out.cp = session.internClass(spec.descriptor); - return WorkspaceErrorInfo{}; - case InstructionKind::Field: - out.cp = session.internFieldRef(spec.owner, spec.name, spec.descriptor); - return WorkspaceErrorInfo{}; - case InstructionKind::Method: - out.cp = spec.opcode == static_cast(Op::INVOKEINTERFACE) - ? session.internInterfaceMethodRef(spec.owner, spec.name, spec.descriptor) - : session.internMethodRef(spec.owner, spec.name, spec.descriptor); - return WorkspaceErrorInfo{}; - case InstructionKind::Jump: - { - const auto found = labels.find(spec.label); - if (spec.label.empty() || found == labels.end()) - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "unknown label: " + spec.label}; - outTarget = found->second; - return WorkspaceErrorInfo{}; - } - case InstructionKind::Iinc: - out.a = spec.operand; - out.b = spec.increment; - out.wide = spec.operand > 255 || spec.increment < -128 || spec.increment > 127; - return WorkspaceErrorInfo{}; - case InstructionKind::TableSwitch: - { - if (spec.maximum < spec.minimum || - static_cast(spec.maximum - spec.minimum + 1) != spec.labels.size()) { - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "tableswitch labels must cover every key from minimum " - "through maximum"}; - } - out.op = Op::TABLESWITCH; // kind 即 opcode(spec.opcode 无需另给) - const auto defaultFound = labels.find(spec.defaultLabel); - if (spec.defaultLabel.empty() || defaultFound == labels.end()) - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "unknown label: " + spec.defaultLabel}; - outTarget = defaultFound->second; - for (const std::string& id : spec.labels) { - const auto found = labels.find(id); - if (id.empty() || found == labels.end()) - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "unknown label: " + id}; - out.targets.push_back(found->second); - } - out.a = spec.minimum; - out.b = spec.maximum; // low..high 与 targets 数量一致(importFrom 复核口径) - return WorkspaceErrorInfo{}; - } - case InstructionKind::LookupSwitch: - { - if (spec.keys.size() != spec.labels.size() || - !std::is_sorted(spec.keys.begin(), spec.keys.end()) || - std::adjacent_find(spec.keys.begin(), spec.keys.end()) != spec.keys.end()) { - return WorkspaceErrorInfo{ - WorkspaceError::InvalidArgument, - "lookupswitch keys and labels must have equal size with unique ascending keys"}; - } - out.op = Op::LOOKUPSWITCH; // kind 即 opcode(spec.opcode 无需另给) - const auto defaultFound = labels.find(spec.defaultLabel); - if (spec.defaultLabel.empty() || defaultFound == labels.end()) - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "unknown label: " + spec.defaultLabel}; - outTarget = defaultFound->second; - for (const std::string& id : spec.labels) { - const auto found = labels.find(id); - if (id.empty() || found == labels.end()) - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "unknown label: " + id}; - out.targets.push_back(found->second); - } - out.keys = spec.keys; - return WorkspaceErrorInfo{}; - } - case InstructionKind::MultiANewArray: - if (spec.descriptor.empty() || spec.dimensions < 1 || spec.dimensions > 255) { - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "multianewarray requires an array descriptor and " - "dimensions in 1..255"}; - } - out.op = Op::MULTIANEWARRAY; // kind 即 opcode(spec.opcode 无需另给) - out.cp = session.internClass(spec.descriptor); - out.a = spec.dimensions; - return WorkspaceErrorInfo{}; - case InstructionKind::LdcString: - out.op = Op::LDC; // 索引超 u8 时经 widenLdcIfNeeded 拓宽为 LDC_W - out.cp = session.internString(spec.stringValue); - return WorkspaceErrorInfo{}; - case InstructionKind::LdcInt: - out.op = Op::LDC; - out.cp = session.internInteger(spec.operand); - return WorkspaceErrorInfo{}; - case InstructionKind::LdcLong: - out.op = Op::LDC2_W; - out.cp = cf.cp.addLong(spec.longValue); - return WorkspaceErrorInfo{}; - case InstructionKind::LdcFloat: - { - ca::u32 bits = 0; - std::memcpy(&bits, &spec.floatValue, sizeof(bits)); - out.op = Op::LDC; - out.cp = cf.cp.addFloatBits(bits); - return WorkspaceErrorInfo{}; - } - case InstructionKind::LdcDouble: - { - ca::u64 bits = 0; - std::memcpy(&bits, &spec.doubleValue, sizeof(bits)); - out.op = Op::LDC2_W; - out.cp = cf.cp.addDoubleBits(bits); - return WorkspaceErrorInfo{}; - } - case InstructionKind::LdcType: - if (spec.descriptor.empty()) - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "LDC type descriptor is required"}; - out.op = Op::LDC; - out.cp = session.internClass(spec.descriptor); - return WorkspaceErrorInfo{}; - case InstructionKind::LdcHandle: - case InstructionKind::LdcDynamic: - case InstructionKind::InvokeDynamic: - // libjbc 常量池无 MethodHandle/MethodType/Dynamic/InvokeDynamic 追加 - // API(主线缺口)——入口即拒,产物不出半成品。 - return WorkspaceErrorInfo{ - WorkspaceError::InvalidArgument, - "method handle and dynamic constants are not supported by the libjbc constant " - "pool append surface"}; - default: - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "unsupported instruction specification"}; - } -} - -/// @brief LDC 的 u8 CP 索引拓宽(自动拓宽等价物)。 -void widenLdcIfNeeded(Insn& insn) -{ - if ((insn.op == Op::LDC || insn.op == Op::LDC_W) && insn.cp > 255) insn.op = Op::LDC_W; -} - -/// @brief 在 labels 里找符号标签;空名/未知均按 "unknown label: " 报错。 -WorkspaceErrorInfo findSymbolicLabel(const std::unordered_map& labels, - const std::string& id, LabelId& out) -{ - const auto found = labels.find(id); - if (id.empty() || found == labels.end()) - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, "unknown label: " + id}; - out = found->second; - return WorkspaceErrorInfo{}; -} - -/// @brief 标签是否已锚定到具体指令(悬空/锚方法末都算未锚定)。 -bool labelAnchored(const jbc::ir::CodeDom& dom, LabelId label) -{ - auto resolved = dom.resolveLabel(label); - if (resolved.is_err()) return false; - return !std::move(resolved).unwrap().isEnd(); -} - -/// @brief COMPUTE_MAXS 等价物:帧引擎重算 maxStack/maxLocals 并叠加下限 -/// (异常处理帧 ≥1 栈槽;maxLocals ≥ 参数槽位 + 非 static 的 this 槽)。 -WorkspaceErrorInfo recomputeFrameMaxes(ClassSession& session, jbc::ir::CodeUnit& unit, - const jbc::core::MemberInfo& method) -{ - jbc::analysis::FrameEngine engine(session.backing().cp); - const jbc::analysis::FrameSummary summary = engine.compute(unit); - const std::string descriptor = utf8Text(session.backing(), method.descCp); - const ca::i32 parsed = jbc::analysis::methodArgSlots(descriptor); - const ca::u32 argSlots = static_cast(parsed < 0 ? 0 : parsed); - const ca::u32 localsFloor = argSlots + ((method.access & kAccStatic) != 0 ? 0U : 1U); - ca::u32 maxStack = summary.maxStack; - if (!unit.exc.empty()) maxStack = std::max(maxStack, 1U); - unit.maxStack = static_cast(std::max(maxStack, 0U)); - unit.maxLocals = static_cast(std::max(summary.maxLocals, localsFloor)); - return WorkspaceErrorInfo{}; -} - -// ── 方法体构建(libjbc CodeUnit → PC 形态 BuiltBody)──────────────────── - -struct BuiltBody -{ - std::vector code; - std::vector excTable; - ca::u16 maxStack = 0; - ca::u16 maxLocals = 0; - std::vector lines; - std::vector lvt; - std::vector lvtt; -}; - -/// @brief 构建整方法体:指令序列 + 异常表 + 行号/局部变量调试表 + COMPUTE_MAXS。 -/// -/// 标签语义按 libjbc 重定型:Label 规格是「下一条指令的前置边界」(方法末尾 -/// 悬空时锚到方法末),Line 规格绑定到下一条指令的导出 PC。try/catch 边界 -/// 先符号标签、后代码指令序号(序号边界是 libjbc 面「标签非指令」的等价物, -/// 序号须恰是标签边界)。局部变量/行号行由导出 PC 落表。 -WorkspaceErrorInfo buildMethodBody(ClassSession& session, - jbc::core::MemberInfo& method, - const std::vector& specs, - const std::vector& blocks, - const std::vector& locals, - BuiltBody& out) -{ - // 标签 ID 唯一且非空(前置校验)。 - std::unordered_set labelIds; - for (const InstructionSpec& spec : specs) { - if (spec.kind != InstructionKind::Label) continue; - if (spec.label.empty() || !labelIds.insert(spec.label).second) - return error(WorkspaceError::InvalidArgument, "replace method body", - "label IDs must be non-empty and unique"); - } - - jbc::ir::CodeUnit unit; - std::unordered_map labels; - for (const InstructionSpec& spec : specs) { - if (spec.kind != InstructionKind::Label) continue; - auto created = unit.dom.createLabel(); - if (created.is_err()) // 理论不可达:新建 DOM 上 createLabel 恒成功 - return error(WorkspaceError::Internal, "replace method body", - "label allocation failed"); - labels.emplace(spec.label, std::move(created).unwrap()); - } - - // pendingAnchors:待绑定到下一条指令的标签(Label 规格落位于自身位置); - // pendingLines:待定行号。 - std::vector pendingAnchors; - pendingAnchors.reserve(labels.size()); - std::vector> pendingLines; // {line, anchor} - - for (const InstructionSpec& spec : specs) { - if (spec.kind == InstructionKind::Label) { - // 标签 = 下一条指令的前置边界;入队等待下一条真实指令锚定。 - pendingAnchors.push_back(labels.find(spec.label)->second); - continue; - } - if (spec.kind == InstructionKind::Line) { - if (spec.line < 1) - return error(WorkspaceError::InvalidArgument, "replace method body", - "line number must be positive"); - pendingLines.emplace_back(spec.line, kNoInsn); - continue; - } - - Insn insn; - LabelId target; - WorkspaceErrorInfo status = makeInstruction(session, spec, labels, insn, target); - if (status.is_err()) - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "replace method body: " + status.message}; - widenLdcIfNeeded(insn); - - // 当前 libjbc append 校验要求分支目标已锚定;前向引用(标签尚未锚定) - // 先以无操作数形态落位,append 后经 get() 原位换回真实 op 并写 target。 - const bool deferredBranch = target != LabelId{} && !labelAnchored(unit.dom, target); - const Op realOp = insn.op; - if (deferredBranch) insn.op = Op::NOP; - - auto appended = unit.dom.append(std::move(insn)); - if (appended.is_err()) - return error(WorkspaceError::InvalidArgument, "replace method body", - "instruction append failed (" + - std::to_string( - static_cast(std::move(appended).unwrap_err())) + - ") at spec kind=" + std::to_string(static_cast(spec.kind)) + - " op=" + std::to_string(static_cast(realOp))); - const InsnId id = std::move(appended).unwrap(); - - if (target != LabelId{}) { - auto* mutableInsn = std::move(unit.dom.get(id)).unwrap(); - if (deferredBranch) mutableInsn->op = realOp; - mutableInsn->target = target; - } - for (LabelId anchor : pendingAnchors) (void)unit.dom.bindLabel(anchor, id); - pendingAnchors.clear(); - for (auto& [line, anchor] : pendingLines) - if (anchor == kNoInsn) anchor = id; - } - // 悬空标签锚到方法末;悬空行号同。 - for (LabelId anchor : pendingAnchors) (void)unit.dom.bindLabel(anchor); - pendingAnchors.clear(); - - // 异常表:符号标签优先,否则代码指令序号(锚到该指令前置边界)。 - const auto resolveBoundary = [&](const std::string& id, ca::i32 index, - LabelId& outLabel) -> WorkspaceErrorInfo { - if (!id.empty()) return findSymbolicLabel(labels, id, outLabel); - const auto& order = unit.dom.order(); - if (index < 0 || static_cast(index) >= order.size()) - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "all boundaries must identify bound labels"}; - auto created = unit.dom.createLabel(order[static_cast(index)]); - if (created.is_err()) - return error(WorkspaceError::InvalidArgument, "replace method body", - "boundary label allocation failed"); - outLabel = std::move(created).unwrap(); - return WorkspaceErrorInfo{}; - }; - for (const TryCatchSpec& block : blocks) { - LabelId start; - LabelId end; - LabelId handler; - WorkspaceErrorInfo status = - resolveBoundary(block.startLabel, block.startInstruction, start); - if (status.is_err()) return status; - status = resolveBoundary(block.endLabel, block.endInstruction, end); - if (status.is_err()) return status; - status = resolveBoundary(block.handlerLabel, block.handlerInstruction, handler); - if (status.is_err()) return status; - unit.exc.push_back( - {start, end, handler, block.type.empty() ? 0U : session.internClass(block.type)}); - } - - // 导出字节并解析标签 PC。 - auto raw = unit.dom.exportRaw(); - if (raw.is_err()) - return error(WorkspaceError::InvalidArgument, "replace method body", - "method body export failed (" + - std::to_string(static_cast(std::move(raw).unwrap_err())) + ")"); - std::vector insns = std::move(raw).unwrap(); - auto encoded = jbc::core::encodeInsns(insns); - if (encoded.is_err()) - return error(WorkspaceError::InvalidArgument, "replace method body", - "method body encode failed (" + - std::to_string(static_cast(std::move(encoded).unwrap_err())) + - ")"); - out.code = std::move(encoded).unwrap(); - - const auto& order = unit.dom.order(); - // InsnId 无 std::hash 特化;order 规模即方法指令数,线性查找足够。 - const auto indexOf = [&order](InsnId id) -> std::optional { - for (ca::usize i = 0; i < order.size(); ++i) { - if (order[i] == id) return i; - } - return std::nullopt; - }; - const ca::u32 codeLength = static_cast(out.code.size()); - const auto pcOf = [&](LabelId label) -> ca::u32 { - auto resolved = unit.dom.resolveLabel(label); - if (resolved.is_err()) return codeLength; - const auto target = std::move(resolved).unwrap(); - if (target.isEnd()) return codeLength; - const auto found = indexOf(target.insn); - if (!found.has_value()) return codeLength; - return insns[*found].pc; - }; - - for (const jbc::ir::CodeUnit::Exc& entry : unit.exc) { - CodeAttr::ExcEntry row; - row.startPc = pcOf(entry.start); - row.endPc = pcOf(entry.end); - row.handlerPc = pcOf(entry.handler); - row.catchType = entry.catchCp; - out.excTable.push_back(row); - } - - for (const auto& [line, anchor] : pendingLines) { - ca::u32 pc = codeLength; - if (anchor != kNoInsn) { - const auto found = indexOf(anchor); - if (found.has_value()) pc = insns[*found].pc; - } - out.lines.push_back({pc, static_cast(line)}); - } - - for (const LocalVariableSpec& local : locals) { - LabelId start; - LabelId end; - WorkspaceErrorInfo status = findSymbolicLabel(labels, local.startLabel, start); - if (status.is_err() || local.name.empty() || local.descriptor.empty() || - local.index < 0) - return error(WorkspaceError::InvalidArgument, "replace method body", - status.message.empty() - ? std::string("invalid local variable definition") - : status.message); - status = findSymbolicLabel(labels, local.endLabel, end); - if (status.is_err()) - return error(WorkspaceError::InvalidArgument, "replace method body", - status.message.empty() - ? std::string("invalid local variable definition") - : status.message); - const ca::u32 startPc = pcOf(start); - const ca::u32 endPc = pcOf(end); - if (endPc < startPc) - return error(WorkspaceError::InvalidArgument, "replace method body", - "invalid local variable definition"); - jbc::core::LocalVariableTableAttr::Row row; - row.startPc = startPc; - row.length = endPc - startPc; - row.nameCp = support::internUtf8(session.backing(), local.name); - row.descCp = support::internUtf8( - session.backing(), local.signature.empty() ? local.descriptor : local.signature); - row.slot = static_cast(local.index); - if (local.signature.empty()) - out.lvt.push_back(row); - else - out.lvtt.push_back(row); - } - - WorkspaceErrorInfo maxes = recomputeFrameMaxes(session, unit, method); - if (maxes.is_err()) return maxes; - out.maxStack = unit.maxStack; - out.maxLocals = unit.maxLocals; - return WorkspaceErrorInfo{}; -} - -/// @brief 把 BuiltBody 写进 Code 属性体(新建 CodeAttr + 调试表嵌套槽)。 -/// 整体重建不带 StackMapTable(pass-contract §9)。 -void applyBuiltBody(ClassSession& session, jbc::core::AttrSlot& slot, const BuiltBody& body) -{ - auto code = std::make_unique(); - code->maxStack = body.maxStack; - code->maxLocals = body.maxLocals; - code->code = body.code; - code->excTable = body.excTable; - const auto lineName = support::internUtf8(session.backing(), "LineNumberTable"); - const auto lvtName = support::internUtf8(session.backing(), "LocalVariableTable"); - const auto lvttName = support::internUtf8(session.backing(), "LocalVariableTypeTable"); - if (!body.lines.empty()) { - auto lines = std::make_unique(); - lines->rows = body.lines; - code->attrs.push_back(jbc::core::AttrSlot::makeNew(lineName, std::move(lines))); - } - if (!body.lvt.empty()) { - auto lvt = std::make_unique(); - lvt->rows = body.lvt; - code->attrs.push_back(jbc::core::AttrSlot::makeNew(lvtName, std::move(lvt))); - } - if (!body.lvtt.empty()) { - auto lvtt = std::make_unique(); - lvtt->rows = body.lvtt; - code->attrs.push_back(jbc::core::AttrSlot::makeNew(lvttName, std::move(lvtt))); - } - slot.body = std::move(code); - slot.state = jbc::core::AttrSlot::State::Modified; -} - -/// @brief 把注解附着位置的属性槽补齐为单注解形态(槽不存在时新建)。 -jbc::core::AttrSlot* ensureAnnotationSlot(jbc::core::ClassFile& cf, - std::vector& attrs, - bool visible) -{ - const char* name = visible ? "RuntimeVisibleAnnotations" : "RuntimeInvisibleAnnotations"; - if (auto* slot = support::findAttr(cf, attrs, name)) return slot; - attrs.push_back(jbc::core::AttrSlot::makeNew( - support::internUtf8(cf, name), std::make_unique())); - return &attrs.back(); -} - -} // namespace - -jasm::Result ClassMutationService::applyJasm( - const std::string& uri, ca::str::Utf16StringRef sourceName, - ca::str::Utf16StringRef source, jasm::ValidationMode validationMode) -{ - // 重建管线的校验门跟随请求档位:off 时 assemble 后跳过 validate。 - jasm::JvmJasmContext context; - context.validationMode = validationMode; - jasm::ResourceJasmService service(*container_); - return service.assemble(uri, sourceName, source, context); -} - -ClassMutationOutcome ClassMutationService::apply(const ClassMutation& mutation, - jasm::ValidationMode validationMode) -{ - const auto failure = [](WorkspaceErrorInfo status) { - ClassMutationOutcome result; - result.status = std::move(status); - return result; - }; - - if (mutation.classUri.empty()) - return failure( - error(WorkspaceError::InvalidArgument, "mutate class", "class URI is empty")); - model::IResource* rawResource = container_->getResource(mutation.classUri); - if (!ca::core::tag_cast::isa(rawResource)) - return failure(error(WorkspaceError::NotFound, "mutate class", mutation.classUri)); - auto* resource = ca::core::tag_cast::cast(rawResource); - - try { - auto parsed = jbc::ir::makeSessionFromBytes(jbc::core::ByteView(resource->get())); - if (parsed.is_err()) - return failure( - error(WorkspaceError::DataLoss, "mutate class", "class bytes cannot be parsed")); - std::shared_ptr session = std::move(parsed).unwrap(); - jbc::core::ClassFile& cf = session->backing(); - - WorkspaceErrorInfo status; - switch (mutation.kind) { - case ClassMutationKind::SetClassAccess: - { - status = validateAccess(mutation.access, AnnotationTarget::Class, "set class access"); - if (status.is_ok()) { - // 旧字段旧语义(版本兼容契约:旧工程重放按整体替换解释); - // 0x20000 的 deprecated 位不再静默丢弃——映射为 Deprecated 属性。 - cf.access = static_cast(mutation.access & 0xFFFF); - if ((mutation.access & kAccDeprecated) != 0) setDeprecatedAttribute(cf, true); - } - break; - } - case ClassMutationKind::ChangeClassAccess: - { - // 增量语义:set 置位、clear 清位、未提及位一律保留。 - ca::u32 setMask = 0; - ca::u32 clearMask = 0; - bool setDeprecated = false; - bool clearDeprecated = false; - status = - parseClassAccessFlagList(mutation.setFlags, "set", setMask, setDeprecated); - if (status.is_err()) break; - status = - parseClassAccessFlagList(mutation.clearFlags, "clear", clearMask, clearDeprecated); - if (status.is_err()) break; - ca::u32 conflict = setMask & clearMask; - if (conflict != 0) { - std::string names; - for (const auto& entry : {std::pair{"public", kAccPublic}, - {"final", kAccFinal}, - {"super", kAccSuper}, - {"interface", kAccInterface}, - {"abstract", kAccAbstract}, - {"synthetic", kAccSynthetic}, - {"annotation", kAccAnnotation}, - {"enum", kAccEnum}, - {"module", kAccModule}, - {"record", kAccRecord}, - {"sealed", kAccSealed}}) { - if ((conflict & entry.second) != 0) { - if (!names.empty()) names += ", "; - names += entry.first; - } - } - status = error(WorkspaceError::InvalidArgument, "change class access", - "flag cannot be both set and cleared: " + names); - break; - } - const ca::u32 merged = - (static_cast(cf.access) & ~clearMask) | setMask; - // 结果位校验与整体替换同规则(可见性互斥 / final+abstract 等)。 - status = validateAccess(static_cast(merged), AnnotationTarget::Class, - "change class access"); - if (status.is_err()) break; - cf.access = static_cast(merged); - if (setDeprecated) setDeprecatedAttribute(cf, true); - if (clearDeprecated) setDeprecatedAttribute(cf, false); - break; - } - case ClassMutationKind::SetFieldAccess: - { - jbc::core::MemberInfo* field = findField(cf, mutation.member); - if (field == nullptr) - status = error(WorkspaceError::NotFound, "set field access", mutation.member.name); - else { - status = - validateAccess(mutation.access, AnnotationTarget::Field, "set field access"); - if (status.is_ok()) field->access = static_cast(mutation.access); - } - break; - } - case ClassMutationKind::SetMethodAccess: - { - jbc::core::MemberInfo* method = findMethod(cf, mutation.member); - if (method == nullptr) - status = error(WorkspaceError::NotFound, "set method access", mutation.member.name); - else { - status = validateAccess(mutation.access, AnnotationTarget::Method, - "set method access"); - if (status.is_ok()) { - const bool dropsBody = (mutation.access & (kAccAbstract | kAccNative)) != 0; - if (dropsBody && support::ensureCode(cf, *method) != nullptr) - status = error(WorkspaceError::InvalidArgument, "set method access", - "cannot mark a method with an existing body abstract/" - "native"); - else - method->access = static_cast(mutation.access); - } - } - break; - } - case ClassMutationKind::AddField: - { - if (findField(cf, mutation.member) != nullptr) { - status = error(WorkspaceError::AlreadyExists, "add field", mutation.member.name); - break; - } - if (mutation.member.name.empty() || mutation.member.descriptor.empty()) { - status = error(WorkspaceError::InvalidArgument, "add field", - "name and descriptor are required"); - break; - } - status = validateAccess(mutation.access, AnnotationTarget::Field, "add field"); - if (status.is_err()) break; - status = fieldConstant(mutation); - if (status.is_err()) break; - - jbc::core::MemberInfo field; - field.access = static_cast(mutation.access); - field.nameCp = support::internUtf8(cf, mutation.member.name); - field.descCp = support::internUtf8(cf, mutation.member.descriptor); - if (!mutation.signature.empty()) { - auto sig = std::make_unique(); - sig->signatureCp = support::internUtf8(cf, mutation.signature); - field.attrs.push_back(jbc::core::AttrSlot::makeNew( - support::internUtf8(cf, "Signature"), std::move(sig))); - } - if (mutation.hasFieldValue) { - const ca::u32 valueIndex = makeConstantCp(*session, mutation.fieldValue); - const std::vector payload{static_cast(valueIndex >> 8), - static_cast(valueIndex)}; - field.attrs.push_back(jbc::core::AttrSlot::makeNew( - support::internUtf8(cf, "ConstantValue"), - std::make_unique(std::move(payload)))); - } - cf.fields.push_back(std::move(field)); - break; - } - case ClassMutationKind::RemoveField: - { - bool removed = false; - for (auto it = cf.fields.begin(); it != cf.fields.end(); ++it) { - if (utf8Text(cf, it->nameCp) == mutation.member.name && - utf8Text(cf, it->descCp) == mutation.member.descriptor) { - cf.fields.erase(it); - removed = true; - break; - } - } - if (!removed) - status = error(WorkspaceError::NotFound, "remove field", mutation.member.name); - break; - } - case ClassMutationKind::AddMethod: - { - if (findMethod(cf, mutation.member) != nullptr) { - status = error(WorkspaceError::AlreadyExists, "add method", mutation.member.name); - break; - } - if (mutation.member.name.empty() || mutation.member.descriptor.empty()) { - status = error(WorkspaceError::InvalidArgument, "add method", - "name and descriptor are required"); - break; - } - status = validateAccess(mutation.access, AnnotationTarget::Method, "add method"); - if (status.is_err()) break; - const bool bodyForbidden = (mutation.access & (kAccAbstract | kAccNative)) != 0; - if (mutation.initialInstructions.empty() && !bodyForbidden) { - status = error(WorkspaceError::InvalidArgument, "add method", - "non abstract/native methods require initial instructions"); - break; - } - if (!mutation.initialInstructions.empty() && bodyForbidden) { - status = error(WorkspaceError::InvalidArgument, "add method", - "abstract/native methods cannot define initial instructions"); - break; - } - bool exceptionInvalid = false; - for (const std::string& exception : mutation.exceptions) { - if (exception.empty()) { - status = error(WorkspaceError::InvalidArgument, "add method", - "exception internal names must be non-empty"); - exceptionInvalid = true; - break; - } - } - if (exceptionInvalid) break; - - jbc::core::MemberInfo method; - method.access = static_cast(mutation.access); - method.nameCp = support::internUtf8(cf, mutation.member.name); - method.descCp = support::internUtf8(cf, mutation.member.descriptor); - if (!mutation.signature.empty()) { - auto sig = std::make_unique(); - sig->signatureCp = support::internUtf8(cf, mutation.signature); - method.attrs.push_back(jbc::core::AttrSlot::makeNew( - support::internUtf8(cf, "Signature"), std::move(sig))); - } - if (!mutation.exceptions.empty()) { - // typed ExceptionsAttr(B3 分派接线后 Raw 形不再是该属性的 - // 会话内合法形态;字节输出与旧 Raw 手拼逐字节一致)。 - auto exc = std::make_unique(); - exc->exceptionCps.reserve(mutation.exceptions.size()); - for (const std::string& exception : mutation.exceptions) { - exc->exceptionCps.push_back(session->internClass(exception)); - } - method.attrs.push_back(jbc::core::AttrSlot::makeNew( - support::internUtf8(cf, "Exceptions"), std::move(exc))); - } - if (!bodyForbidden) { - BuiltBody body; - status = buildMethodBody(*session, method, mutation.initialInstructions, - mutation.tryCatchBlocks, mutation.localVariables, body); - if (status.is_err()) break; - jbc::core::AttrSlot slot = jbc::core::AttrSlot::makeNew( - support::internUtf8(cf, "Code"), std::make_unique()); - applyBuiltBody(*session, slot, body); - method.attrs.push_back(std::move(slot)); - } - cf.methods.push_back(std::move(method)); - break; - } - case ClassMutationKind::RemoveMethod: - { - bool removed = false; - for (auto it = cf.methods.begin(); it != cf.methods.end(); ++it) { - if (utf8Text(cf, it->nameCp) == mutation.member.name && - utf8Text(cf, it->descCp) == mutation.member.descriptor) { - cf.methods.erase(it); - removed = true; - break; - } - } - if (!removed) - status = error(WorkspaceError::NotFound, "remove method", mutation.member.name); - break; - } - case ClassMutationKind::AddMarkerAnnotation: - case ClassMutationKind::RemoveAnnotation: - { - if (mutation.annotationDescriptor.empty()) { - status = error(WorkspaceError::InvalidArgument, "edit annotation", - "annotation descriptor is required"); - break; - } - bool visible = true; - jbc::core::MemberInfo* member = nullptr; - switch (mutation.annotationTarget) { - case AnnotationTarget::Class: break; - case AnnotationTarget::Field: member = findField(cf, mutation.member); break; - case AnnotationTarget::Method: member = findMethod(cf, mutation.member); break; - } - if (mutation.annotationTarget != AnnotationTarget::Class && member == nullptr) { - status = error(WorkspaceError::NotFound, "edit annotation", - mutation.member.name.empty() ? mutation.classUri - : mutation.member.name); - break; - } - jbc::core::AttrSlot* slot = ensureAnnotationSlot( - cf, member != nullptr ? member->attrs : cf.attrs, visible); - if (!slot->decoded()) { - if (cf.decodeAttr(*slot).is_err()) { - status = error(WorkspaceError::DataLoss, "edit annotation", - "annotation attribute cannot be decoded"); - break; - } - } - // 槽本体非 const、由候选容器持有;peek() 的 const 面在此收窄回写路径。 - auto* annotations = const_cast( - ca::core::tag_cast::cast(slot->peek())); - if (annotations == nullptr) { - status = error(WorkspaceError::DataLoss, "edit annotation", - "annotation attribute has an unexpected shape"); - break; - } - if (mutation.kind == ClassMutationKind::AddMarkerAnnotation) { - const bool duplicate = std::any_of( - annotations->annos.begin(), annotations->annos.end(), - [&](const jbc::core::AnnotationsAttr::Annotation& item) { - return utf8Text(cf, item.typeCp) == mutation.annotationDescriptor; - }); - if (duplicate) { - status = error(WorkspaceError::AlreadyExists, "add annotation", - mutation.annotationDescriptor); - break; - } - jbc::core::AnnotationsAttr::Annotation anno; - anno.typeCp = support::internUtf8(cf, mutation.annotationDescriptor); - (void)slot->mutate(); - annotations->annos.push_back(std::move(anno)); - break; - } - bool removed = false; - for (auto it = annotations->annos.begin(); it != annotations->annos.end(); ++it) { - if (utf8Text(cf, it->typeCp) == mutation.annotationDescriptor) { - (void)slot->mutate(); - annotations->annos.erase(it); - removed = true; - break; - } - } - if (!removed) - status = error(WorkspaceError::NotFound, "remove annotation", - mutation.annotationDescriptor); - break; - } - case ClassMutationKind::RenameLocal: - { - jbc::core::MemberInfo* method = findMethod(cf, mutation.member); - if (method == nullptr) { - status = error(WorkspaceError::NotFound, "rename local", mutation.member.name); - break; - } - bool changed = false; - if (jbc::core::AttrSlot* codeSlot = support::findAttr(cf, method->attrs, "Code")) { - if (!codeSlot->decoded()) (void)cf.decodeAttr(*codeSlot); - // 槽本体非 const、由候选容器持有;peek() 的 const 面在此收窄回写路径。 - auto* code = const_cast( - ca::core::tag_cast::cast(codeSlot->peek())); - if (code != nullptr) { - for (jbc::core::AttrSlot& nested : code->attrs) { - if (!cf.cp.utf8Equals(nested.nameCp, "LocalVariableTable") && - !cf.cp.utf8Equals(nested.nameCp, "LocalVariableTypeTable")) - continue; - if (!nested.decoded()) { - if (cf.decodeAttr(nested).is_err()) continue; - } - auto* table = const_cast( - ca::core::tag_cast::cast( - nested.peek())); - if (table == nullptr) continue; - bool tableChanged = false; - for (jbc::core::LocalVariableTableAttr::Row& row : table->rows) { - if (utf8Text(cf, row.nameCp) != mutation.localName) continue; - if (mutation.localIndex >= 0 && - static_cast(row.slot) != mutation.localIndex) - continue; - row.nameCp = support::internUtf8(cf, mutation.replacementLocalName); - tableChanged = true; - } - if (tableChanged) { - (void)nested.mutate(); - changed = true; - } - } - } - } - if (!changed) - status = error(WorkspaceError::NotFound, "rename local", mutation.localName); - break; - } - case ClassMutationKind::ReplaceTryCatchBlocks: - { - jbc::core::MemberInfo* method = findMethod(cf, mutation.member); - if (method == nullptr) { - status = error(WorkspaceError::NotFound, "replace try catch blocks", - mutation.member.name); - break; - } - // 空块 + 无绑定:照旧语义为成功无操作(不动方法体)。 - if (mutation.tryCatchBlocks.empty() && mutation.existingLabels.empty()) break; - const ca::usize methodIndex = - static_cast(method - cf.methods.data()); - auto promoted = jbc::ir::promoteMethod(*session, methodIndex); - if (promoted.is_err()) { - status = error(WorkspaceError::InvalidArgument, "replace try catch blocks", - "all boundaries must identify bound labels"); - break; - } - jbc::ir::CodeUnitRef unit = std::move(promoted).unwrap(); - - std::unordered_map labels; - bool bindingInvalid = false; - for (const LabelBindingSpec& binding : mutation.existingLabels) { - if (binding.id.empty() || labels.find(binding.id) != labels.end()) { - status = error(WorkspaceError::InvalidArgument, "bind labels", - "label IDs must be non-empty and unique"); - bindingInvalid = true; - break; - } - const auto& order = unit->dom.order(); - if (binding.instructionIndex < 0 || - static_cast(binding.instructionIndex) >= order.size()) { - status = error(WorkspaceError::InvalidArgument, "bind labels", - "label instruction index is outside the method"); - bindingInvalid = true; - break; - } - auto created = unit->dom.createLabel( - order[static_cast(binding.instructionIndex)]); - if (created.is_err()) { - status = error(WorkspaceError::InvalidArgument, "bind labels", - "label allocation failed"); - bindingInvalid = true; - break; - } - labels.emplace(binding.id, std::move(created).unwrap()); - } - if (bindingInvalid) break; - - const auto resolve = [&](const std::string& id, ca::i32 index, - LabelId& outLabel) -> WorkspaceErrorInfo { - if (!id.empty()) return findSymbolicLabel(labels, id, outLabel); - const auto& order = unit->dom.order(); - if (index < 0 || static_cast(index) >= order.size()) - return WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "all boundaries must identify bound labels"}; - auto created = unit->dom.createLabel(order[static_cast(index)]); - if (created.is_err()) - return error(WorkspaceError::InvalidArgument, "replace try catch blocks", - "boundary label allocation failed"); - outLabel = std::move(created).unwrap(); - return WorkspaceErrorInfo{}; - }; - unit->exc.clear(); - bool boundaryInvalid = false; - for (const TryCatchSpec& block : mutation.tryCatchBlocks) { - LabelId start; - LabelId end; - LabelId handler; - status = resolve(block.startLabel, block.startInstruction, start); - if (status.is_err()) { - boundaryInvalid = true; - break; - } - status = resolve(block.endLabel, block.endInstruction, end); - if (status.is_err()) { - boundaryInvalid = true; - break; - } - status = resolve(block.handlerLabel, block.handlerInstruction, handler); - if (status.is_err()) { - boundaryInvalid = true; - break; - } - unit->exc.push_back({start, end, handler, - block.type.empty() ? 0U : session->internClass(block.type)}); - } - if (boundaryInvalid) break; - status = recomputeFrameMaxes(*session, *unit, *method); - if (status.is_err()) break; - auto lowered = jbc::ir::lowerMethod(*session, methodIndex); - if (lowered.is_err()) { - status = error(WorkspaceError::InvalidArgument, "replace try catch blocks", - "method body cannot be written back"); - break; - } - break; - } - case ClassMutationKind::ReplaceMethodBody: - { - jbc::core::MemberInfo* method = findMethod(cf, mutation.member); - if (method == nullptr) { - status = error(WorkspaceError::NotFound, "replace method body", - mutation.member.name); - break; - } - if ((method->access & (kAccAbstract | kAccNative)) != 0) { - status = error(WorkspaceError::InvalidArgument, "replace method body", - "abstract/native methods cannot have a body"); - break; - } - if (mutation.initialInstructions.empty()) { - status = error(WorkspaceError::InvalidArgument, "replace method body", - "instructions are required"); - break; - } - BuiltBody body; - status = buildMethodBody(*session, *method, mutation.initialInstructions, - mutation.tryCatchBlocks, mutation.localVariables, body); - if (status.is_err()) break; - // 整体重建:旧 Code 槽原位换体(含丢弃全部旧嵌套属性—— - // COMPUTE_FRAMES + 空标签集合同样丢弃旧 LNT/LVT/SMT)。 - jbc::core::AttrSlot* codeSlot = support::findAttr(cf, method->attrs, "Code"); - if (codeSlot == nullptr) { - method->attrs.push_back(jbc::core::AttrSlot::makeNew( - support::internUtf8(cf, "Code"), std::make_unique())); - codeSlot = &method->attrs.back(); - } - else if (!codeSlot->decoded()) { - (void)cf.decodeAttr(*codeSlot); - } - applyBuiltBody(*session, *codeSlot, body); - break; - } - case ClassMutationKind::InsertInstruction: - case ClassMutationKind::ReplaceInstruction: - case ClassMutationKind::RemoveInstruction: - { - jbc::core::MemberInfo* method = findMethod(cf, mutation.member); - if (method == nullptr) { - status = error(WorkspaceError::NotFound, "edit instruction", mutation.member.name); - break; - } - const ca::usize methodIndex = static_cast(method - cf.methods.data()); - if (!jbc::ir::hasCode(*session, methodIndex)) { - status = error(WorkspaceError::InvalidArgument, "edit instruction", - "instruction index is outside the method"); - break; - } - auto promoted = jbc::ir::promoteMethod(*session, methodIndex); - if (promoted.is_err()) { - status = error(WorkspaceError::DataLoss, "edit instruction", - "method body cannot be promoted"); - break; - } - jbc::ir::CodeUnitRef unit = std::move(promoted).unwrap(); - const auto& order = unit->dom.order(); - const ca::i32 index = mutation.instructionIndex; - if (index < 0 || static_cast(index) >= order.size()) { - status = error(WorkspaceError::InvalidArgument, "edit instruction", - "instruction index is outside the method"); - break; - } - const InsnId targetId = order[static_cast(index)]; - - // 符号标签绑定(existingLabels:绑定 ID → 指令序号)。 - std::unordered_map labels; - bool bindingInvalid = false; - for (const LabelBindingSpec& binding : mutation.existingLabels) { - if (binding.id.empty() || labels.find(binding.id) != labels.end()) { - status = error(WorkspaceError::InvalidArgument, "bind labels", - "label IDs must be non-empty and unique"); - bindingInvalid = true; - break; - } - if (binding.instructionIndex < 0 || - static_cast(binding.instructionIndex) >= order.size()) { - status = error(WorkspaceError::InvalidArgument, "bind labels", - "label instruction index is outside the method"); - bindingInvalid = true; - break; - } - auto created = unit->dom.createLabel( - order[static_cast(binding.instructionIndex)]); - if (created.is_err()) { - status = error(WorkspaceError::InvalidArgument, "bind labels", - "label allocation failed"); - bindingInvalid = true; - break; - } - labels.emplace(binding.id, std::move(created).unwrap()); - } - if (bindingInvalid) break; - - if (mutation.kind == ClassMutationKind::RemoveInstruction) { - auto erased = unit->dom.erase(targetId); - if (erased.is_err()) { - status = error( - WorkspaceError::InvalidArgument, "edit instruction", - "instruction is still referenced by branches or exception boundaries"); - break; - } - } - else if (mutation.kind == ClassMutationKind::ReplaceInstruction) { - if (mutation.instruction.kind == InstructionKind::Label) { - // libjbc 面标签是边界不是指令:以标签替指令不可表达。 - status = error(WorkspaceError::InvalidArgument, "edit instruction", - "replacing an instruction with a label is not supported"); - break; - } - Insn insn; - LabelId target; - status = makeInstruction(*session, mutation.instruction, labels, insn, target); - if (status.is_err()) { - status = WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "edit instruction: " + status.message}; - break; - } - widenLdcIfNeeded(insn); - auto* slot = std::move(unit->dom.get(targetId)).unwrap(); - // 保留旧 srcPc:pcmap 以旧 PC 重定向 LNT(relocation contract)。 - const ca::u32 oldPc = slot->srcPc; - insn.srcPc = oldPc; - if (target != LabelId{}) insn.target = target; - *slot = std::move(insn); - } - else // InsertInstruction - { - if (mutation.instruction.kind == InstructionKind::Label) { - if (mutation.instruction.label.empty() || - labels.find(mutation.instruction.label) != labels.end()) { - status = error(WorkspaceError::InvalidArgument, "edit instruction", - "new label ID must be non-empty and not already bound"); - break; - } - // 插入标签 = 在插入点创建前置边界:insertBefore 锚到目标 - // 指令;insertAfter 锚到后继(末尾则锚方法末)。 - InsnId anchor = kNoInsn; - if (mutation.insertAfter) { - const bool atEnd = static_cast(index) + 1 >= order.size(); - anchor = atEnd ? kNoInsn : order[static_cast(index) + 1]; - } - else { - anchor = targetId; - } - auto created = unit->dom.createLabel(anchor); - if (created.is_err()) { - status = error(WorkspaceError::InvalidArgument, "edit instruction", - "label allocation failed"); - break; - } - break; - } - Insn insn; - LabelId target; - status = makeInstruction(*session, mutation.instruction, labels, insn, target); - if (status.is_err()) { - status = WorkspaceErrorInfo{WorkspaceError::InvalidArgument, - "edit instruction: " + status.message}; - break; - } - widenLdcIfNeeded(insn); - auto inserted = mutation.insertAfter - ? unit->dom.insertAfter(targetId, std::move(insn)) - : unit->dom.insertBefore(targetId, std::move(insn)); - if (inserted.is_err()) { - status = error(WorkspaceError::InvalidArgument, "edit instruction", - "instruction insert failed"); - break; - } - const InsnId newId = std::move(inserted).unwrap(); - if (target != LabelId{}) { - auto* slot = std::move(unit->dom.get(newId)).unwrap(); - slot->target = target; - } - } - if (status.is_err()) break; - - status = recomputeFrameMaxes(*session, *unit, *method); - if (status.is_err()) break; - auto lowered = jbc::ir::lowerMethod(*session, methodIndex); - if (lowered.is_err()) { - status = error(WorkspaceError::InvalidArgument, "edit instruction", - "method body cannot be written back"); - break; - } - break; - } - } - if (status.is_err()) return failure(std::move(status)); - - // 写回:jbc 序列化 → Jasm 验证(写回后整类门,档位随请求:off - // 直通,与 jasm 重建路径同语义)→ spi 两阶段提交。 - auto written = jbc::core::writeClass(cf); - if (written.is_err()) - return failure(error(WorkspaceError::Internal, "mutate class", - "class serialization failed")); - std::vector bytes = std::move(written).unwrap(); - - jasm::JvmJasmContext gateContext; - gateContext.validationMode = validationMode; - jasm::JvmJasmService validator; - auto validated = validator.validate(bytes, gateContext); - if (!validated.isOk()) { - ClassMutationOutcome result; - result.status = error(WorkspaceError::InvalidArgument, "mutate class", - "candidate class did not pass validation"); - result.diagnostics = std::move(validated.diagnostics); - return result; - } - spi::ClassResourcePendingCommit pending; - pending.stage(resource, std::move(bytes)); - pending.commit(); - ClassMutationOutcome result; - result.diagnostics = std::move(validated.diagnostics); - return result; - } - catch (const std::exception& exception) { - return failure(error(WorkspaceError::Internal, "mutate class", exception.what())); - } -} - -} // namespace mjbase::workspace diff --git a/mjbase/src/mjbase/workspace/class_mutation_service.hpp b/mjbase/src/mjbase/workspace/class_mutation_service.hpp deleted file mode 100644 index 2f8aab7..0000000 --- a/mjbase/src/mjbase/workspace/class_mutation_service.hpp +++ /dev/null @@ -1,270 +0,0 @@ -#pragma once - -#include -#include - -#include "libca/core/datatype.hpp" - -#include "mjbase/model/resource_container.hpp" -#include "mjbase/model/resource_model.hpp" -#include "mjbase/workspace/workspace_types.hpp" - -namespace mjbase::workspace { - -/// @file class_mutation_service.hpp -/// @brief 结构化 class 修改服务(事务与校验语义,指令载体按 libjbc 定型)。 - -/// @brief 结构化 class 修改的目标成员标识。 -struct MemberSelector final -{ - std::string name; - std::string descriptor; -}; - -/// @brief 可由结构化编辑接口构造的常用 JVM 指令种类。 -enum class InstructionKind : ca::u8 -{ - Simple, - Int, - Var, - Type, - Field, - Method, - Jump, - Label, - LdcString, - LdcInt, - LdcLong, - LdcFloat, - LdcDouble, - LdcType, - LdcHandle, - LdcDynamic, - InvokeDynamic, - Iinc, - TableSwitch, - LookupSwitch, - MultiANewArray, - Line, -}; - -/// @brief JVM method handle 的拥有型请求表示。 -struct HandleSpec final -{ - ca::i32 tag = 0; - std::string owner; - std::string name; - std::string descriptor; - bool interfaceReference = false; -}; - -/// @brief LDC、invokedynamic 和 condy bootstrap 参数支持的常量种类。 -enum class ConstantKind : ca::u8 -{ - String, - Integer, - Long, - Float, - Double, - Type, - Handle, - Dynamic, -}; - -/// @brief 不持有 IR 句柄的 JVM 常量请求。 -struct ConstantSpec final -{ - ConstantKind kind = ConstantKind::String; - std::string stringValue; - ca::i32 intValue = 0; - ca::i64 longValue = 0; - float floatValue = 0.0F; - double doubleValue = 0.0; - std::string descriptor; - std::string name; - HandleSpec handle; - HandleSpec bootstrapMethod; - std::vector bootstrapArguments; -}; - -/// @brief 单条结构化字节码指令;字段按 @p kind 解释。 -/// -/// 索引语义(类型面定型):`instructionIndex`/label 绑定的 -/// instructionIndex 均为方法体内**代码指令序号**(CodeDom 程序顺序,不含 -/// label/line 节点——mjbase 底座中标签是句柄不是指令)。 -struct InstructionSpec final -{ - InstructionKind kind = InstructionKind::Simple; - ca::i32 opcode = -1; - ca::i32 operand = 0; - ca::i32 increment = 0; - bool interfaceInvocation = false; - std::string owner; - std::string name; - std::string descriptor; - std::string stringValue; - ca::i64 longValue = 0; - float floatValue = 0.0F; - double doubleValue = 0.0; - std::string label; - std::string defaultLabel; - std::vector labels; - std::vector keys; - ca::i32 minimum = 0; - ca::i32 maximum = -1; - ca::i32 dimensions = 0; - ca::i32 line = 0; - HandleSpec handle; - HandleSpec bootstrapMethod; - std::vector bootstrapArguments; -}; - -/// @brief 以代码指令序号或符号 label 描述的 try/catch 块边界。 -struct TryCatchSpec final -{ - ca::i32 startInstruction = -1; - ca::i32 endInstruction = -1; - ca::i32 handlerInstruction = -1; - std::string type; - std::string startLabel; - std::string endLabel; - std::string handlerLabel; -}; - -/// @brief 将请求内符号 label 绑定到现有方法的代码指令序号(指令前置边界)。 -struct LabelBindingSpec final -{ - std::string id; - ca::i32 instructionIndex = -1; -}; - -/// @brief 整方法体替换时重建的局部变量调试信息。 -struct LocalVariableSpec final -{ - std::string name; - std::string descriptor; - std::string signature; - std::string startLabel; - std::string endLabel; - ca::i32 index = -1; -}; - -/// @brief ClassMutationService 支持的便利编辑操作。 -enum class ClassMutationKind : ca::u8 -{ - SetClassAccess, - ChangeClassAccess, ///< 增量访问位编辑(set/clear 名单,未提及位保留) - SetFieldAccess, - SetMethodAccess, - AddField, - RemoveField, - AddMethod, - RemoveMethod, - AddMarkerAnnotation, - RemoveAnnotation, - RenameLocal, - ReplaceTryCatchBlocks, - ReplaceMethodBody, - InsertInstruction, - ReplaceInstruction, - RemoveInstruction, -}; - -/// @brief 结构化 annotation 修改的附着位置。 -enum class AnnotationTarget : ca::u8 -{ - Class, - Field, - Method, -}; - -/// @brief 一次只修改一个 class 的结构化变更请求。 -/// -/// `classUri` 是工作区 class 资源 URI;涉及成员的操作使用 `member` 精确消除重载歧义。 -/// AddMethod 的非 abstract/native 方法必须提供能独立通过验证的 `initialInstructions`。 -/// -/// 访问位编辑的语义分界(版本兼容契约): -/// - `kind = SetClassAccess` + `access`(旧字段,保留):整体重建 access 位—— -/// 旧工程/旧调用点的既有语义照旧重放;access 里的 0x20000 是 Deprecated -/// 属性不是 flag,映射为属性追加(历史版本静默丢弃,禁止再丢)。 -/// - `kind = ChangeClassAccess` + `setFlags`/`clearFlags`(新 op,新语义): -/// 增量编辑——set 置位、clear 清位、未提及位一律保留(ACC_SUPER 等 -/// 不可表达 flag 不再丢失);"deprecated" 名字映射为 Deprecated 属性增删。 -/// 未知 flag 名报错并列出 class 目标合法 flag 全集。 -struct ClassMutation final -{ - std::string classUri; - ClassMutationKind kind = ClassMutationKind::SetClassAccess; - MemberSelector member; - ca::i32 access = 0; - /// @brief ChangeClassAccess:置位 flag 名单(JVM 小写助记,如 "public")。 - std::vector setFlags; - /// @brief ChangeClassAccess:清位 flag 名单;两组都未提及的位保留原值。 - std::vector clearFlags; - ca::i32 instructionIndex = -1; - bool insertAfter = false; - ca::i32 localIndex = -1; - std::string localName; - std::string replacementLocalName; - AnnotationTarget annotationTarget = AnnotationTarget::Class; - bool annotationVisible = true; - std::string annotationDescriptor; - InstructionSpec instruction; - std::string signature; - std::vector exceptions; - bool hasFieldValue = false; - ConstantSpec fieldValue; - std::vector existingLabels; - std::vector initialInstructions; - std::vector tryCatchBlocks; - std::vector localVariables; -}; - -/// @brief 候选 class 修改的底层结果;Workspace 将其包装为统一 MutationResult。 -struct ClassMutationOutcome final -{ - WorkspaceErrorInfo status; - std::vector diagnostics; -}; - -/// @brief 在一个候选 ResourceContainer 中执行完整 Jasm 或结构化 class 修改。 -/// -/// 本服务不持有 revision、历史或原始快照。调用方必须在隔离的候选容器上使用它,并仅在 -/// `status` 成功后提交候选容器。完整 Jasm 修改委托 ResourceJasmService;结构化修改 -/// 按libjbc 会话改写(骨架元数据直改 backing,方法体经 promote/CodeDom/lower 或 -/// 整体重建),产物统一经 spi::sanitizeClassFile 与 Jasm 验证后再写回。 -/// -/// COMPUTE_FRAMES 式 StackMapTable 重算在 libjbc 写回面无对应物(pass-contract §9), -/// 布局改变的产物沿用原属性的 relocation 语义(lower 链路),整方法重建的产物 -/// 不携带重建的 StackMapTable。 -/// -/// 与 mbase 版的差异:构造不再挂 GlobalAnalysisManager——mjbase ResourceJasmService -/// 每次按容器即时重建层次图,服务不持任何跨调用缓存。 -class ClassMutationService final -{ -public: - explicit ClassMutationService(model::ResourceContainer& container) - : container_(&container) - {} - - /// @brief 汇编完整 Jasm 并事务替换候选容器中的目标 class。 - /// validationMode 同时控制重建管线自带的字节码校验门:off 时重建 - /// 后不再做 validate(汇编本身仍必须成功)。 - jasm::Result applyJasm(const std::string& uri, - ca::str::Utf16StringRef sourceName, - ca::str::Utf16StringRef source, - jasm::ValidationMode validationMode - = jasm::ValidationMode::Relaxed); - - /// @brief 应用一项结构化修改;失败时不会替换目标 ClassResource 的字节。 - /// validationMode 控制写回后的整类校验门(off 同样跳过),与 - /// jasm 重建路径同档位语义。 - ClassMutationOutcome apply(const ClassMutation& mutation, - jasm::ValidationMode validationMode - = jasm::ValidationMode::Relaxed); - -private: - model::ResourceContainer* container_; -}; - -} // namespace mjbase::workspace diff --git a/mjbase/src/mjbase/workspace/classfile_support.cpp b/mjbase/src/mjbase/workspace/classfile_support.cpp deleted file mode 100644 index 7ab189a..0000000 --- a/mjbase/src/mjbase/workspace/classfile_support.cpp +++ /dev/null @@ -1,308 +0,0 @@ -#include "mjbase/workspace/classfile_support.hpp" - -#include -#include -#include - -namespace mjbase::workspace::support { - -namespace { - -using jbc::core::AnnotationsAttr; -using jbc::core::ConstantPool; -using jbc::core::CpTag; - -/// @brief u2 大端读取(Raw 载荷解析)。 -ca::u32 readU2(const std::vector& bytes, ca::usize at) -{ - return (static_cast(bytes[at]) << 8) | bytes[at + 1]; -} - -} // namespace - -std::string renderFloatBits(ca::u32 bits) -{ - float value = 0.0F; - std::memcpy(&value, &bits, sizeof(value)); - std::ostringstream out; - out << value; - return out.str(); -} - -std::string renderDoubleBits(ca::u64 bits) -{ - double value = 0.0; - std::memcpy(&value, &bits, sizeof(value)); - std::ostringstream out; - out << value; - return out.str(); -} - -std::string annotationValueText(const jbc::core::ClassFile& cf, - const AnnotationsAttr::ElemValue& value) -{ - const ConstantPool::Entry* entry = - cf.cp.valid(value.cp) ? &cf.cp.at(value.cp) : nullptr; - switch (value.tag) { - case 's': - // String 条目:a 指向 Utf8。 - if (entry != nullptr && entry->tag == CpTag::String) return utf8Text(cf, entry->a); - return {}; - case 'e': - return utf8Text(cf, value.typeNameCp) + "." + utf8Text(cf, value.constNameCp); - case 'c': - return utf8Text(cf, value.cp); - case 'Z': - return entry != nullptr && entry->ival != 0 ? "true" : "false"; - case 'B': - case 'C': - case 'S': - case 'I': - return entry != nullptr ? std::to_string(entry->ival) : std::string("0"); - case 'J': - return entry != nullptr ? std::to_string(entry->lval) : std::string("0"); - case 'F': - return entry != nullptr ? renderFloatBits(static_cast(entry->ival)) - : std::string("0"); - case 'D': - return entry != nullptr ? renderDoubleBits(static_cast(entry->lval)) - : std::string("0"); - case '@': - return value.nested != nullptr ? utf8Text(cf, value.nested->typeCp) : std::string(); - default: - return {}; - } -} - -void collectAnnotationElement(const jbc::core::ClassFile& cf, std::string_view name, - const AnnotationsAttr::ElemValue& value, AnnotationElement& out) -{ - out.name = std::string(name); - out.tag = static_cast(value.tag); - out.value = annotationValueText(cf, value); - if (value.tag == '@' && value.nested != nullptr) { - out.annotationDescriptor = utf8Text(cf, value.nested->typeCp); - for (const auto& [nameCp, nested] : value.nested->values) { - AnnotationElement child; - collectAnnotationElement(cf, utf8Of(cf, nameCp), nested, child); - out.children.push_back(std::move(child)); - } - } - else if (value.tag == '[') { - for (const AnnotationsAttr::ElemValue& item : value.items) { - AnnotationElement child; - collectAnnotationElement(cf, "", item, child); - out.children.push_back(std::move(child)); - } - } -} - -void collectAnnotations(jbc::core::ClassFile& cf, std::vector& attrs, - std::vector& out) -{ - for (jbc::core::AttrSlot& slot : attrs) { - const bool visible = cf.cp.utf8Equals(slot.nameCp, "RuntimeVisibleAnnotations"); - if (!visible && !cf.cp.utf8Equals(slot.nameCp, "RuntimeInvisibleAnnotations")) continue; - if (!slot.decoded()) { - if (cf.decodeAttr(slot).is_err()) continue; - } - const auto* body = ca::core::tag_cast::cast(slot.peek()); - if (body == nullptr) continue; - for (const AnnotationsAttr::Annotation& anno : body->annos) { - AnnotationInfo info; - info.descriptor = utf8Text(cf, anno.typeCp); - info.visible = visible; - for (const auto& [nameCp, elem] : anno.values) { - AnnotationElement element; - collectAnnotationElement(cf, utf8Of(cf, nameCp), elem, element); - info.elements.push_back(std::move(element)); - } - out.push_back(std::move(info)); - } - } -} - -void collectLocalVariables(jbc::core::ClassFile& cf, jbc::core::CodeAttr& code, - std::vector& out) -{ - for (jbc::core::AttrSlot& slot : code.attrs) { - const bool typeTable = cf.cp.utf8Equals(slot.nameCp, "LocalVariableTypeTable"); - if (!typeTable && !cf.cp.utf8Equals(slot.nameCp, "LocalVariableTable")) continue; - if (!slot.decoded()) { - if (cf.decodeAttr(slot).is_err()) continue; - } - const auto* body = ca::core::tag_cast::cast(slot.peek()); - if (body == nullptr) continue; - for (const jbc::core::LocalVariableTableAttr::Row& row : body->rows) { - LocalVariableInfo info; - info.name = utf8Text(cf, row.nameCp); - info.descriptor = utf8Text(cf, row.descCp); - info.index = static_cast(row.slot); - out.push_back(std::move(info)); - } - } -} - -std::optional fieldConstantValue(jbc::core::ClassFile& cf, - jbc::core::MemberInfo& field) -{ - const jbc::core::AttrSlot* slot = findAttr(cf, field.attrs, "ConstantValue"); - if (slot == nullptr) return std::nullopt; - if (!slot->decoded()) { - if (cf.decodeAttr(const_cast(*slot)).is_err()) return std::nullopt; - } - // 0.4.16+ typed 面:解码命中 ConstantValueAttr(constantValueCp 单域); - // 拒收形态落 Raw 槽 2 字节原读。cast 无检查,kind 分道先于 cast。 - ca::u32 cpIndex = 0; - if (slot->peek()->kind() == jbc::core::AttrKind::ConstantValue) { - cpIndex = ca::core::tag_cast::cast(slot->peek()) - ->constantValueCp; - } - else { - const auto* raw = ca::core::tag_cast::cast(slot->peek()); - if (raw == nullptr || raw->bytes().size() < 2) return std::nullopt; - cpIndex = readU2(raw->bytes(), 0); - } - if (!cf.cp.valid(cpIndex)) return std::nullopt; - const ConstantPool::Entry& entry = cf.cp.at(cpIndex); - ConstantValueInfo info; - switch (entry.tag) { - case CpTag::Integer: - info.kind = ConstantValueInfo::Kind::Integer; - info.text = std::to_string(entry.ival); - break; - case CpTag::Long: - info.kind = ConstantValueInfo::Kind::Long; - info.text = std::to_string(entry.lval); - break; - case CpTag::Float: - info.kind = ConstantValueInfo::Kind::Float; - info.text = renderFloatBits(static_cast(entry.ival)); - break; - case CpTag::Double: - info.kind = ConstantValueInfo::Kind::Double; - info.text = renderDoubleBits(static_cast(entry.lval)); - break; - case CpTag::String: - info.kind = ConstantValueInfo::Kind::String; - info.text = utf8Text(cf, entry.a); - break; - default: - return std::nullopt; - } - return info; -} - -ConstantPoolEntryInfo constantPoolEntry(const jbc::core::ClassFile& cf, ca::u32 index, bool resolve) -{ - ConstantPoolEntryInfo info; - info.index = index; - if (!cf.cp.valid(index)) { - info.tag = ""; - info.text = ""; - info.malformed = true; - return info; - } - const ConstantPool::Entry& entry = cf.cp.at(index); - switch (entry.tag) { - case CpTag::Utf8: - info.tag = "Utf8"; - info.text = utf8Text(cf, index); - break; - case CpTag::Integer: - info.tag = "Integer"; - info.text = std::to_string(entry.ival); - break; - case CpTag::Float: - info.tag = "Float"; - info.text = renderFloatBits(static_cast(entry.ival)); - break; - case CpTag::Long: - info.tag = "Long"; - info.text = std::to_string(entry.lval); - break; - case CpTag::Double: - info.tag = "Double"; - info.text = renderDoubleBits(static_cast(entry.lval)); - break; - case CpTag::Class: - info.tag = "Class"; - info.text = utf8Text(cf, entry.a); - break; - case CpTag::String: - info.tag = "String"; - info.text = utf8Text(cf, entry.a); - break; - case CpTag::Fieldref: - case CpTag::Methodref: - case CpTag::InterfaceMethodref: { - info.tag = entry.tag == CpTag::Fieldref ? "Fieldref" - : entry.tag == CpTag::Methodref ? "Methodref" - : "InterfaceMethodref"; - if (!resolve) break; - // 引用条目:class_index → Class,name_and_type_index → name + desc。 - const std::string owner = classNameOf(cf, entry.a); - std::string name; - std::string desc; - if (cf.cp.valid(entry.b)) { - const ConstantPool::Entry& nt = cf.cp.at(entry.b); - if (nt.tag == CpTag::NameAndType) { - name = utf8Text(cf, nt.a); - desc = utf8Text(cf, nt.b); - } - } - info.text = owner + "." + name + ":" + desc; - break; - } - case CpTag::NameAndType: - info.tag = "NameAndType"; - // 引用类条目与 workspace_types.hpp 的 resolve 契约对齐:仅 resolve 时给文本。 - if (resolve) info.text = utf8Text(cf, entry.a) + ":" + utf8Text(cf, entry.b); - break; - case CpTag::MethodHandle: - info.tag = "MethodHandle"; - // a = reference_kind,b = 引用条目;文本仅 resolve 时给。 - if (resolve) { - info.text = "kind " + std::to_string(entry.a) + " -> " + - constantPoolEntry(cf, entry.b, true).text; - } - break; - case CpTag::MethodType: - info.tag = "MethodType"; - info.text = utf8Text(cf, entry.a); - break; - case CpTag::Dynamic: - case CpTag::InvokeDynamic: - info.tag = entry.tag == CpTag::Dynamic ? "Dynamic" : "InvokeDynamic"; - if (resolve) { - std::string nameAndDesc; - if (cf.cp.valid(entry.b)) { - const ConstantPool::Entry& nt = cf.cp.at(entry.b); - if (nt.tag == CpTag::NameAndType) - nameAndDesc = utf8Text(cf, nt.a) + ":" + utf8Text(cf, nt.b); - } - info.text = nameAndDesc + " @bsm " + std::to_string(entry.a); - } - break; - case CpTag::Module: - info.tag = "Module"; - info.text = utf8Text(cf, entry.a); - break; - case CpTag::Package: - info.tag = "Package"; - info.text = utf8Text(cf, entry.a); - break; - } - return info; -} - -std::string objectClassFromArrayDescriptor(std::string_view descriptor) -{ - // 剥掉全部 '[' 后必须是 'L'+name+';' 才有对象类;基本类型数组返回空。 - ca::usize at = 0; - while (at < descriptor.size() && descriptor[at] == '[') ++at; - if (at == 0 || at >= descriptor.size()) return {}; - return objectClassFromFieldDescriptor(descriptor.substr(at)); -} - -} // namespace mjbase::workspace::support diff --git a/mjbase/src/mjbase/workspace/classfile_support.hpp b/mjbase/src/mjbase/workspace/classfile_support.hpp deleted file mode 100644 index 81e645e..0000000 --- a/mjbase/src/mjbase/workspace/classfile_support.hpp +++ /dev/null @@ -1,223 +0,0 @@ -/// @file classfile_support.hpp -/// @brief workspace 模块内共享的 jbc classfile 低层助手(只读取面为主)。 -/// -/// class_mutation_service.cpp(写路径)与 workspace.cpp(查询路径)共同使用 -/// 的骨架读取/属性定位/属性摘要工具。定位属性一律按名字逐槽比较字节—— -/// 常量池 addUtf8 只追加不去重,新写属性名的 CP 索引可能与解析期同名条目 -/// 不同,索引等值不可靠。 -#pragma once - -#include -#include -#include -#include -#include - -#include "jbc/core/attr_codec.hpp" -#include "jbc/core/attribute.hpp" -#include "jbc/core/class_file.hpp" -#include "jbc/core/constant_pool.hpp" - -#include "mjbase/workspace/workspace_types.hpp" - -namespace mjbase::workspace::support { - -/// @brief 常量池 Utf8 条目文本;越界/非 Utf8 返回空视图。 -inline std::string_view utf8Of(const jbc::core::ClassFile& cf, ca::u32 index) noexcept -{ - return cf.cp.utf8Of(index); -} - -/// @brief 常量池 Utf8 条目文本的 owning 拷贝(跨界传递用)。 -inline std::string utf8Text(const jbc::core::ClassFile& cf, ca::u32 index) -{ - const std::string_view view = cf.cp.utf8Of(index); - return std::string(view); -} - -/// @brief Class 条目 → 内部名;非 Class/越界返回空。 -inline std::string classNameOf(const jbc::core::ClassFile& cf, ca::u32 classCp) -{ - if (!cf.cp.valid(classCp)) return {}; - const jbc::core::ConstantPool::Entry& entry = cf.cp.at(classCp); - if (entry.tag != jbc::core::CpTag::Class) return {}; - return utf8Text(cf, entry.a); -} - -/// @brief 常量池内已有的 Utf8 索引;无则 kNotFound(=0,0 号槽恒为占位)。 -inline constexpr ca::u32 kUtf8NotFound = 0; - -/// @brief 扫描式 Utf8 intern:命中已有条目返回原索引,miss 才 addUtf8 追加。 -/// -/// 属性名/调试表名等「要求与解析期条目等值」的场合必须走这里;纯新增数据 -/// (常量值、新成员名)可直接 add* 追加,不影响正确性只影响体积。 -inline ca::u32 internUtf8(jbc::core::ClassFile& cf, std::string_view text) -{ - const jbc::core::ConstantPool& pool = cf.cp; - for (ca::u32 i = 1; i < pool.size(); ++i) { - if (pool.isUtf8(i) && pool.utf8Equals(i, text)) return i; - } - return cf.cp.addUtf8(text); -} - -/// @brief 在属性槽列表中按名字定位(逐槽比较名字字节)。 -inline const jbc::core::AttrSlot* findAttr(const jbc::core::ClassFile& cf, - const std::vector& attrs, - std::string_view name) -{ - for (const jbc::core::AttrSlot& slot : attrs) { - if (cf.cp.utf8Equals(slot.nameCp, name)) return &slot; - } - return nullptr; -} - -/// @brief findAttr 的可变版本(写路径)。 -inline jbc::core::AttrSlot* findAttr(jbc::core::ClassFile& cf, - std::vector& attrs, - std::string_view name) -{ - for (jbc::core::AttrSlot& slot : attrs) { - if (cf.cp.utf8Equals(slot.nameCp, name)) return &slot; - } - return nullptr; -} - -/// @brief 成员方法体的 Code 槽定位并按需解码(lazy 状态 Untouched → Decoded, -/// 写回仍是 copy-through);无 Code 属性(abstract/native)返回 nullptr。 -inline jbc::core::CodeAttr* ensureCode(jbc::core::ClassFile& cf, jbc::core::MemberInfo& member) -{ - jbc::core::AttrSlot* slot = findAttr(cf, member.attrs, "Code"); - if (slot == nullptr) return nullptr; - if (!slot->decoded()) { - if (cf.decodeAttr(*slot).is_err()) return nullptr; - } - // 槽本体非 const、由调用方持有;peek() 的 const 面在此收窄回写路径。 - return const_cast( - ca::core::tag_cast::cast(slot->peek())); -} - -/// @brief ensureCode 的 const 前置解码版(查询路径;slot 状态只做 lazy 解码)。 -inline const jbc::core::CodeAttr* codeOf(jbc::core::ClassFile& cf, const jbc::core::MemberInfo& member) -{ - return ensureCode(cf, const_cast(member)); -} - -/// @brief Signature 属性文本;无该属性返回空。 -inline std::string signatureText(jbc::core::ClassFile& cf, std::vector& attrs) -{ - const jbc::core::AttrSlot* slot = findAttr(cf, attrs, "Signature"); - if (slot == nullptr) return {}; - // Signature 是 typed 属性(SignatureAttr,u2 utf8 载荷)。 - if (!slot->decoded()) { - if (cf.decodeAttr(const_cast(*slot)).is_err()) return {}; - } - const auto* attr = ca::core::tag_cast::cast(slot->peek()); - if (attr == nullptr) return {}; - return utf8Text(cf, attr->signatureCp); -} - -/// @brief 成员 Exceptions 属性的异常类内部名列表;无该属性返回空。 -/// -/// typed ExceptionsAttr 体经公开解码入口读出(B3 分派接线前后同形);槽已有 -/// 体时(如 class_mutation_service 新建的 New 态槽,无源 span)按体的实际 -/// 形态读。解码失败(半截体)按查询面口径返回空。 -inline std::vector exceptionNames(jbc::core::ClassFile& cf, - jbc::core::MemberInfo& member) -{ - std::vector names; - const jbc::core::AttrSlot* slot = findAttr(cf, member.attrs, "Exceptions"); - if (slot == nullptr) return names; - if (slot->decoded()) { - if (const auto* typed = - ca::core::tag_cast::cast(slot->peek())) { - names.reserve(typed->exceptionCps.size()); - for (const ca::u32 classCp : typed->exceptionCps) { - names.push_back(classNameOf(cf, classCp)); - } - } - return names; - } - auto d = jbc::core::decodeExceptionsBody(cf, *slot); - if (d.is_err()) return names; - const auto body = std::move(d).unwrap(); - const auto& exc = static_cast(*body); - names.reserve(exc.exceptionCps.size()); - for (const ca::u32 classCp : exc.exceptionCps) { - names.push_back(classNameOf(cf, classCp)); - } - return names; -} - -/// @brief 注解元素值 → 中性渲染文本(字符串不加引号、数值无后缀)。 -/// @details 与 ConstantValueInfo 同一口径;枚举渲染 "type.const",class 渲染 -/// 描述符原文,嵌套注解渲染类型描述符。 -std::string annotationValueText(const jbc::core::ClassFile& cf, - const jbc::core::AnnotationsAttr::ElemValue& value); - -/// @brief 递归展开一个注解元素(name 允许为空——数组项无键名)。 -void collectAnnotationElement(const jbc::core::ClassFile& cf, std::string_view name, - const jbc::core::AnnotationsAttr::ElemValue& value, - AnnotationElement& out); - -/// @brief 收集类/字段/方法上的 Runtime{Visible,Invisible}Annotations(简单形态)。 -/// 解码失败的槽跳过(查询面对畸形输入宽容)。 -void collectAnnotations(jbc::core::ClassFile& cf, std::vector& attrs, - std::vector& out); - -/// @brief 收集 Code 属性嵌套的 LVT/LVTT 行(ASM 口径:LVTT 行 desc=签名文本, -/// signature 恒空——与 visitLocalVariable 可观察行为一致)。 -void collectLocalVariables(jbc::core::ClassFile& cf, jbc::core::CodeAttr& code, - std::vector& out); - -/// @brief 字段 ConstantValue 属性摘要;无该属性/载荷不完整返回 nullopt。 -std::optional fieldConstantValue(jbc::core::ClassFile& cf, - jbc::core::MemberInfo& field); - -/// @brief 常量池条目摘要:tag 名(JVM 规范常量名)+ 文本 + wide 续槽标记。 -/// @param resolve 引用类条目(Fieldref/Methodref/InterfaceMethodref)是否 -/// 解析为 "owner.name:desc";其余条目文本总是填充。 -ConstantPoolEntryInfo constantPoolEntry(const jbc::core::ClassFile& cf, ca::u32 index, bool resolve); - -/// @brief 常量池第 @p index 号 Utf8 是否为 @p prefix 开头的属性名。 -inline bool attrNameIs(const jbc::core::ClassFile& cf, const jbc::core::AttrSlot& slot, - std::string_view prefix) noexcept -{ - return cf.cp.utf8StartsWith(slot.nameCp, prefix); -} - -/// @brief 字段描述符 → 元素类内部名("Ljava/lang/String;" → "java/lang/String"); -/// 数组/基本类型描述符原样返回(无对象类)。 -inline std::string objectClassFromFieldDescriptor(std::string_view descriptor) -{ - if (descriptor.size() >= 3 && descriptor.front() == 'L' && descriptor.back() == ';') { - return std::string(descriptor.substr(1, descriptor.size() - 2)); - } - return {}; -} - -/// @brief LDC/数组类描述符 → 元素类内部名:"Ljava/lang/String;" → "java/lang/String", -/// "[[I" → "I" 的宿主类语义不成立时返回空;数组取最深元素的对象类。 -/// 数组描述符只对「元素为对象类型」给出内部名(如 -/// "[Ljava/lang/String;" → "java/lang/String"),基本类型数组返回空。 -std::string objectClassFromArrayDescriptor(std::string_view descriptor); - -// @brief Float/Double 位模式 → 中性文本渲染(ostringstream 默认精度)。 -std::string renderFloatBits(ca::u32 bits); -std::string renderDoubleBits(ca::u64 bits); - -/// @brief Float/Double 位模式 → 数值(搜索数值字面量用)。 -inline float bitsToFloat(ca::u32 bits) noexcept -{ - float value = 0.0F; - std::memcpy(&value, &bits, sizeof(value)); - return value; -} - -inline double bitsToDouble(ca::u64 bits) noexcept -{ - double value = 0.0; - std::memcpy(&value, &bits, sizeof(value)); - return value; -} - -} // namespace mjbase::workspace::support diff --git a/mjbase/src/mjbase/workspace/i_workspace.hpp b/mjbase/src/mjbase/workspace/i_workspace.hpp deleted file mode 100644 index 248cf43..0000000 --- a/mjbase/src/mjbase/workspace/i_workspace.hpp +++ /dev/null @@ -1,304 +0,0 @@ -#pragma once - -#include "mjbase/model/container_stack.hpp" -#include "mjbase/workspace/class_mutation_service.hpp" -#include "mjbase/workspace/workspace_types.hpp" - -namespace mjbase::workspace { - -/// @file i_workspace.hpp -/// @brief Workspace 抽象接口:业务契约方法集。 -/// -/// 各项目面向本接口编程;mjbase 提供 WorkspaceBase 基础实现(无业务语义的 -/// jar 打开/资源缓存/搜索与变更骨架)。接口签名类型全部来自 workspace_types.hpp。 -/// -/// 事务契约:原始资源与工作副本分别保存。修改始终先发生 -/// 在候选副本;只有候选完成严格验证(spi::sanitizeClassFile 引用语义预检 + Jasm -/// 验证)后才替换工作副本并增加 revision。调用方用 expectedRevision 可拒绝过期写入。 -/// -/// remap / rename / pipeline / workflow 方法族: -/// - 候选副本语义贯穿:analyze* 与 validate* 不触碰工作副本,apply* 与 -/// runWorkflow 验证通过才原子提交并递增 revision; -/// - pipeline 组成以 mjbase 自有 pass 集为准(spi::PassRegistry 内置表), -/// "default-optimize" 等同名的组成消费方经 -/// availablePasses/availablePipelines 枚举; -/// - PipelineRequest.jdkPath 非空时扫描 JDK 归档建层次图,经 -/// spi::StrictTypeResolverAdapter 接入帧重算(libjbc 写回面无 COMPUTE_*S 旗标)。 -class IWorkspace -{ -public: - /// @brief 接口析构;实现类必须可安全析构。 - IWorkspace() = default; - virtual ~IWorkspace() = default; - - IWorkspace(const IWorkspace&) = delete; - IWorkspace& operator=(const IWorkspace&) = delete; - IWorkspace(IWorkspace&&) = delete; - IWorkspace& operator=(IWorkspace&&) = delete; - - /// @brief 打开主 JAR 并建立不可变原始快照与可修改工作副本。 - virtual WorkspaceResult openJar(const std::string& path) = 0; - - /// @brief 打开单个 classfile,并以其 JVM internal name 建立稳定资源 URI。 - virtual WorkspaceResult openClass(const std::string& path) = 0; - - /// @brief 添加只读 supporting JAR,用于后续层次和调用解析。 - /// @return 返回当前 Workspace 内稳定的 support ID 和内容 fingerprint;重复路径或内容被拒绝。 - virtual WorkspaceResult addSupportingJar(const std::string& path) = 0; - - /// @brief 带角色注册 supporting 容器:library/runtime 挂载只产生一条 - /// 容器记录——注册进同一在册表(路径/内容唯一约束跨角色生效),记录 - /// 的 role 标签区分角色,不再另行追加第二套注册面。 - /// @param role library/runtime(primary 由 project_open 绑定,不接受)。 - /// @return 返回稳定 support ID(detach 的唯一句柄)与内容 fingerprint。 - virtual WorkspaceResult addSupportingContainer( - const std::string& path, model::ContainerRole role) - { - (void)role; - return addSupportingJar(path); - } - - /// @brief 按 support ID 移除只读 supporting JAR,并失效依赖 classpath 的分析缓存。 - /// @return 返回被移除的 supporting resource 描述;ID 不存在时返回 NotFound。 - /// @note 一次移除即清理该容器的全部角色注册(在册表一容器一记录)。 - virtual WorkspaceResult removeSupportingJar(const std::string& id) = 0; - - /// @brief 工作区是否已加载主 class 或 JAR。 - virtual bool isLoaded() const noexcept = 0; - - /// @brief 当前主制品种类;未加载时返回 ArtifactKind::None。 - virtual ArtifactKind artifactKind() const noexcept = 0; - - /// @brief 当前主资源的来源路径。 - virtual const std::string& sourcePath() const noexcept = 0; - - /// @brief 单 class 工作区的稳定资源 URI;其他 kind 返回空字符串。 - virtual const std::string& primaryClassUri() const noexcept = 0; - - /// @brief 当前工作区 revision;只在已提交变更、undo、redo 或 restore 后递增。 - virtual ca::u64 revision() const noexcept = 0; - - /// @brief 当前工作副本是否和原始快照不同。 - virtual bool isDirty() const = 0; - - /// @brief 当前工作副本是否满足导出条件。 - virtual bool canExport() const noexcept = 0; - - /// @brief 返回主资源的稳定排序摘要。 - virtual WorkspaceResult> listResources(bool includeClasses = true) const = 0; - - /// @brief 读取一个主资源的原始字节。 - virtual WorkspaceResult readResource(const std::string& uri) const = 0; - - /// @brief 按资源 URI 或 internal name 读取 class 原始字节和内容 fingerprint。 - /// @note 精确 URI 指向损坏 class 时仍返回 bytes,此时 internalName 为空。 - virtual WorkspaceResult readClassBytes(const std::string& uriOrInternalName) const = 0; - - /// @brief 返回所有主 class 的轻量结构摘要。 - virtual WorkspaceResult> listClassSummaries() const = 0; - - /// @brief 返回所有主 class 的完整结构化元数据。 - virtual WorkspaceResult> listClasses() const = 0; - - /// @brief 通过资源 URI 或 JVM internal name 查询 class 详情。 - virtual WorkspaceResult classInfo(const std::string& uriOrInternalName) const = 0; - - /// @brief 枚举 class 常量池条目。 - /// @param uriOrInternalName 资源 URI 或 internal name。 - /// @param resolve true 时解析引用类条目(FieldRef/MethodRef/InterfaceMethodRef/ - /// NameAndType/InvokeDynamic/Dynamic)的 owner.name:desc 文本填入 text;false 时 - /// 引用类条目的 text 为空。非引用类条目(Utf8/Integer 等)总是填充 text。 - /// @note Long/Double 的第二槽保留空占位条目(malformed=true);损坏 class 经 - /// libjbc 解析失败时返回 DataLoss。 - virtual WorkspaceResult> constantPool( - const std::string& uriOrInternalName, bool resolve) const = 0; - - /// @brief 以 owner/name/descriptor 精确查询方法详情。 - virtual WorkspaceResult methodInfo(const std::string& owner, const std::string& name, - const std::string& descriptor) const = 0; - - /// @brief 以 owner/name/descriptor 精确查询字段详情。 - virtual WorkspaceResult fieldInfo(const std::string& owner, const std::string& name, - const std::string& descriptor) const = 0; - - /// @brief 返回精确选择方法的逐指令摘要及行号元数据。 - virtual WorkspaceResult methodBytecode(const std::string& owner, - const std::string& name, - const std::string& descriptor) const = 0; - - /// @brief 返回精确选择方法的完整指令渲染(保留 CP 索引与字节 PC)。 - /// @param resolve true 时填充 operandComment(解析 CP 引用为 owner.name:desc 等可读文本); - /// false 时 operandComment 为空。 - virtual WorkspaceResult methodInstructions(const std::string& owner, - const std::string& name, - const std::string& descriptor, - bool resolve) const = 0; - - /// @brief 返回精确选择方法的规范化 Jasm 声明。 - virtual jasm::Result getMethodJasm( - const std::string& owner, const std::string& name, const std::string& descriptor) const = 0; - - /// @brief 返回主资源 MANIFEST 的稳定排序主属性。 - virtual WorkspaceResult> manifestAttributes() const = 0; - - /// @brief 提取主资源所有非 class 条目中的最短长度为 @p minimumLength 的可打印字符串。 - virtual WorkspaceResult> resourceStrings( - ca::usize minimumLength = 4) const = 0; - - /// @brief 按 class 内部名或资源 URI 查询 class;损坏 class 会被跳过。 - virtual WorkspaceResult> searchClasses( - const std::string& query, const SearchOptions& options = {}) const = 0; - - /// @brief 按 owner/name/descriptor/signature 查询字段和方法;损坏 class 会被跳过。 - virtual WorkspaceResult> searchMembers( - const std::string& query, const SearchOptions& options = {}) const = 0; - - /// @brief 查询 class 常量池字符串和非 class 可打印资源字符串;损坏 class 会被跳过。 - virtual WorkspaceResult> searchStrings( - const std::string& query, const SearchOptions& options = {}) const = 0; - - /// @brief 精确查询常量池及即时数值字面量;数值文本支持等价的标准数值写法。 - virtual WorkspaceResult> searchNumbers( - const std::string& query, const SearchOptions& options = {}) const = 0; - - /// @brief 按 mnemonic、opcode、节点类型和指令操作数查询方法指令;损坏 class 会被跳过。 - virtual WorkspaceResult> searchInstructions( - const std::string& query, const SearchOptions& options = {}) const = 0; - - /// @brief 精确返回所有编码目标为 owner/name/descriptor 的方法引用。 - virtual WorkspaceResult> xrefsToMethod(const std::string& owner, - const std::string& name, - const std::string& descriptor) const = 0; - - /// @brief 精确返回所有编码目标为 owner/name/descriptor 的字段引用。 - virtual WorkspaceResult> xrefsToField(const std::string& owner, - const std::string& name, - const std::string& descriptor) const = 0; - - /// @brief 返回所有直接或结构性引用 internalName 的位置。 - virtual WorkspaceResult> xrefsToClass(const std::string& internalName) const = 0; - - /// @brief 返回方法的直接调用目标;resolved 为 true 时扩展虚调用和接口调用。 - virtual WorkspaceResult> calleesOfMethod(const std::string& owner, - const std::string& name, - const std::string& descriptor, - bool resolved = false) const = 0; - - /// @brief 返回目标 class 的超类、接口、直接/全部子类和实现者。 - virtual WorkspaceResult classRelations(const std::string& internalName) const = 0; - - /// @brief 返回精确方法所在的继承/实现覆盖组件。 - virtual WorkspaceResult> overridesOfMethod( - const std::string& owner, const std::string& name, const std::string& descriptor) const = 0; - - /// @brief 返回可供 MCP/CLI 直接传输的完整 class JASM。 - virtual jasm::Result getJasm(const std::string& uri) const = 0; - - /// @brief 在不提交的候选副本中汇编并验证完整 class JASM。 - virtual MutationResult validateJasm(const std::string& uri, ca::str::Utf16StringRef sourceName, - ca::str::Utf16StringRef source, - const MutationRequest& request = {}) = 0; - - /// @brief 原子应用完整 class JASM,成功后产生一个新的 revision。 - virtual MutationResult applyJasm(const std::string& uri, ca::str::Utf16StringRef sourceName, - ca::str::Utf16StringRef source, - const MutationRequest& request = {}) = 0; - - /// @brief 验证由专用 classfile adapter 生成的完整 class 候选字节,不提交工作区。 - virtual MutationResult validateClassBytes(const std::string& uri, - const std::vector& classBytes, - const MutationRequest& request = {}) = 0; - - /// @brief 原子应用已由专用 classfile adapter 生成的完整 class 候选字节。 - virtual MutationResult applyClassBytes(const std::string& uri, - const std::vector& classBytes, - const MutationRequest& request = {}) = 0; - - /// @brief 在不提交的候选副本中应用并验证一项结构化 class 修改。 - virtual MutationResult validateClassMutation(const ClassMutation& mutation, - const MutationRequest& request = {}) = 0; - - /// @brief 原子应用一项结构化 class 修改,成功后产生一个新的 revision。 - virtual MutationResult applyClassMutation(const ClassMutation& mutation, - const MutationRequest& request = {}) = 0; - - /// @brief 原子替换一个 class 的原始字节;dry-run 时只返回完整验证和 diff。 - virtual MutationResult replaceClassBytes(const std::string& uriOrInternalName, - const std::vector& bytes, - const MutationRequest& request = {}) = 0; - - /// @brief 返回当前工作副本中相对打开时原始快照已变化的 class。 - virtual WorkspaceResult> modifiedClasses() const = 0; - - /// @brief 在不提交候选副本的情况下分析一个显式 MemberRemapPlan 的影响。 - virtual ImpactAnalysis analyzeRemap(const asm_remapper::MemberRemapPlan& plan, - const asm_remapper::RemapPolicy& policy) const = 0; - - /// @brief 原子应用一个显式 MemberRemapPlan。 - virtual MutationResult applyRemap(const asm_remapper::MemberRemapPlan& plan, - const asm_remapper::RemapPolicy& policy, - const MutationRequest& request = {}) = 0; - - /// @brief 生成重命名映射和风险报告,不修改工作副本。 - virtual RenameAnalysis analyzeRename(const RenameRequest& request) const = 0; - - /// @brief 原子应用一个已通过 override 风险检查的重命名请求。 - virtual MutationResult applyRename(const RenameRequest& request, - const MutationRequest& requestOptions = {}) = 0; - - /// @brief 在不提交候选副本的情况下运行 pass/pipeline 并返回验证和 byte diff。 - virtual MutationResult validatePipeline(const PipelineRequest& pipeline, - const MutationRequest& request = {}) = 0; - - /// @brief 原子执行 pass/pipeline;dryRun 时与 validatePipeline 返回同一类结果。 - virtual MutationResult applyPipeline(const PipelineRequest& pipeline, - const MutationRequest& request = {}) = 0; - - /// @brief 在一个候选事务中执行 remap、可选 pipeline、验证和候选 xref。 - virtual WorkflowResult runWorkflow(const WorkflowRequest& workflow, - const MutationRequest& request = {}) = 0; - - /// @brief 导入一个私有工作流候选 JAR,并按当前 revision 验证、导出和提交。 - /// @warning candidateJarPath 仅限受控后台事务生成的 JAR;提交失败会移除本次创建的 exportPath。 - virtual WorkflowCandidateCommitResult commitWorkflowCandidateJar( - const WorkflowCandidateCommitRequest& candidate, - const MutationRequest& request = {}) = 0; - - /// @brief 比较当前工作副本与一个外部 JAR 的全部可读取资源,不修改工作区。 - virtual JarDiffResult diffJar(const std::string& referencePath) const = 0; - - /// @brief 扫描并预览当前工作副本的 JAR 修复候选,不提交变更。 - virtual JarRepairResult analyzeJarRepair(const JarRepairRequest& repair, - const MutationRequest& request = {}) const = 0; - - /// @brief 原子应用已显式选择的 JAR 修复;dryRun 时返回预览结果。 - virtual JarRepairResult applyJarRepair(const JarRepairRequest& repair, - const MutationRequest& request = {}) = 0; - - /// @brief 将指定 class 恢复到打开工作区时的原始字节。 - virtual MutationResult revertClass(const std::string& uri, const MutationRequest& request = {}) = 0; - - /// @brief 保存当前工作副本的具名快照,不改变 revision。 - virtual WorkspaceResult createSavepoint(const std::string& name) = 0; - - /// @brief 恢复具名快照,产生新 revision。 - virtual MutationResult restoreSavepoint(const std::string& name, - const MutationRequest& request = {}) = 0; - - /// @brief 撤销上一次已提交的事务。 - virtual MutationResult undo(const MutationRequest& request = {}) = 0; - - /// @brief 重做上一次撤销的事务。 - virtual MutationResult redo(const MutationRequest& request = {}) = 0; - - /// @brief 将当前工作副本原子写入一个此前不存在的输出 JAR。 - virtual WorkspaceResult exportJar(const std::string& outputPath) const = 0; - - /// @brief 将单 class 工作副本原子写入一个此前不存在的输出 classfile。 - virtual WorkspaceResult exportClass(const std::string& outputPath) const = 0; - - /// @brief 返回已附加的 supporting JAR 摘要。 - virtual std::vector supportingResources() const = 0; -}; - -} // namespace mjbase::workspace diff --git a/mjbase/src/mjbase/workspace/reference_scan.cpp b/mjbase/src/mjbase/workspace/reference_scan.cpp deleted file mode 100644 index 7a81620..0000000 --- a/mjbase/src/mjbase/workspace/reference_scan.cpp +++ /dev/null @@ -1,250 +0,0 @@ -#include "mjbase/workspace/reference_scan.hpp" - -#include - -#include "jbc/core/opcode.hpp" -#include "jbc/ir/units.hpp" - -#include "mjbase/analysis/cp_refs.hpp" -#include "mjbase/asm/remapper/member_remapper.hpp" -#include "mjbase/model/resource_model.hpp" -#include "mjbase/spi/class_file_sanitize.hpp" -#include "mjbase/workspace/classfile_support.hpp" - -namespace mjbase::workspace { - -namespace { - -using jbc::core::Op; - -/// @brief invoke 家族(invokestatic/special/virtual/interface);indy 由语义面单独分流。 -bool isInvokeOp(Op op) -{ - return op == Op::INVOKEVIRTUAL || op == Op::INVOKESPECIAL || op == Op::INVOKESTATIC || - op == Op::INVOKEINTERFACE; -} - -/// @brief 字段访问家族(getstatic/getfield/putstatic/putfield)。 -bool isFieldAccessOp(Op op) -{ - return op == Op::GETSTATIC || op == Op::GETFIELD || op == Op::PUTSTATIC || op == Op::PUTFIELD; -} - -/// @brief 显式类型指令(new/anewarray/checkcast/instanceof/multianewarray)。 -bool isTypeOp(Op op) -{ - return op == Op::NEW || op == Op::ANEWARRAY || op == Op::CHECKCAST || op == Op::INSTANCEOF || - op == Op::MULTIANEWARRAY; -} - -} // namespace - -void ReferenceScan::build(const model::ResourceContainer& container) -{ - methods_.clear(); - fields_.clear(); - types_.clear(); - container.visitResources([&](std::string_view uri, const model::IResource& resource) { - const auto* classResource = ca::core::tag_cast::dyn_cast(&resource); - if (!classResource) return; - // 净化预检与 WorkspaceBase::validateCandidate 同口径:引用语义损坏的 - // class 不进扫描(畸形 class 的跳过纪律)。 - if (!spi::sanitizeClassFile(classResource->get()).empty()) return; - auto session = jbc::ir::makeSessionFromBytes(jbc::core::ByteView(classResource->get())); - if (session.is_err()) return; - auto held = std::move(session).unwrap(); - - const std::string sourceClass{held->backing().thisName()}; - - // class 头声明:super 与直接接口是 class 级类型引用(无源方法)。 - const auto super = held->backing().superName(); - if (!super.empty()) types_.push_back({std::string{super}, {sourceClass, {}, {}, -1}}); - for (const auto interfaceCp : held->backing().interfaces) { - const auto name = held->backing().className(interfaceCp); - if (!name.empty()) types_.push_back({std::string{name}, {sourceClass, {}, {}, -1}}); - } - - for (auto& visit : jbc::ir::collectCodeUnits(*held)) { - if (visit.kind != jbc::ir::CodeVisit::Kind::Ok) continue; - const jbc::ir::CodeUnit& code = *visit.unit; - const auto& unit = held->unit().methods[visit.methodIdx]; - const std::string methodName{held->unit().symbols().str(unit.name)}; - const std::string methodDesc{held->unit().symbols().str(unit.desc)}; - const ReferenceSite site{sourceClass, methodName, methodDesc, -1}; - - const auto& order = code.dom.order(); - for (ca::usize index = 0; index < order.size(); ++index) { - const auto insnId = order[index]; - const jbc::ir::Insn* insn = code.dom.peek(insnId); - if (!insn) continue; - ReferenceSite at = site; - at.insnIndex = static_cast(index); - - if (isInvokeOp(insn->op)) { - jbc::ir::ClassSession::MemberSem sem; - if (held->memberOf(*insn, sem) && - sem.kind != jbc::ir::ClassSession::MemberSem::Kind::InvokeDynamic && - !sem.owner.empty()) { - methods_.push_back({{std::string{sem.owner}, std::string{sem.name}, - std::string{sem.desc}}, - at, - MethodReferenceKind::Direct}); - types_.push_back({std::string{sem.owner}, at}); - } - continue; - } - if (isFieldAccessOp(insn->op)) { - jbc::ir::ClassSession::MemberSem sem; - if (held->memberOf(*insn, sem) && !sem.owner.empty()) { - fields_.push_back({{std::string{sem.owner}, std::string{sem.name}, - std::string{sem.desc}}, - at, - FieldReferenceKind::Direct}); - types_.push_back({std::string{sem.owner}, at}); - } - continue; - } - if (isTypeOp(insn->op)) { - if (insn->op == Op::MULTIANEWARRAY) { - // multianewarray 的 cp 指向数组描述符;提取元素对象类型。 - const auto element = support::objectClassFromArrayDescriptor( - analysis::resolveClassName(held->backing().cp, insn->cp)); - if (!element.empty()) types_.push_back({std::move(element), at}); - continue; - } - const auto name = held->classOf(*insn); - if (!name.empty()) types_.push_back({std::string{name}, at}); - continue; - } - if (insn->op == Op::LDC || insn->op == Op::LDC_W) { - jbc::ir::ClassSession::ConstSem constant; - if (!held->constOf(*insn, constant)) continue; - if (constant.kind == jbc::ir::ClassSession::ConstSem::Kind::Class && - !constant.str.empty()) { - // 数组形态的 class 常量提取元素对象类型(LDC "[Lfoo/Bar;" → foo/Bar)。 - const auto element = support::objectClassFromArrayDescriptor(constant.str); - types_.push_back({element.empty() ? std::string{constant.str} - : std::move(element), - at}); - continue; - } - // class-string 元数据事实:形态合法的字符串常量是反射引用候选 - //(与 remapper 反射改写候选同一语法,remap 改的 xref 必须可见)。 - if (constant.kind == jbc::ir::ClassSession::ConstSem::Kind::String && - asm_remapper::looksLikeClassName(constant.str)) { - types_.push_back({asm_remapper::toInternalReflectionName(constant.str), - at}); - } - continue; - } - } - } - }); -} - -std::vector ReferenceScan::methodReferencesTo( - const std::string& owner, const std::string& name, const std::string& descriptor) const -{ - std::vector result; - for (const auto& reference : methods_) { - if (reference.target.owner == owner && reference.target.name == name && - reference.target.descriptor == descriptor) { - result.push_back(reference); - } - } - return result; -} - -std::vector ReferenceScan::methodReferencesFrom( - const std::string& owner, const std::string& name, const std::string& descriptor) const -{ - std::vector result; - for (const auto& reference : methods_) { - if (reference.site.sourceClass == owner && reference.site.sourceMethod == name && - reference.site.sourceDescriptor == descriptor) { - result.push_back(reference); - } - } - return result; -} - -std::vector ReferenceScan::methodReferencesFromClass(const std::string& owner) const -{ - std::vector result; - for (const auto& reference : methods_) { - if (reference.site.sourceClass == owner) result.push_back(reference); - } - return result; -} - -std::vector ReferenceScan::fieldReferencesTo( - const std::string& owner, const std::string& name, const std::string& descriptor) const -{ - std::vector result; - for (const auto& reference : fields_) { - if (reference.target.owner == owner && reference.target.name == name && - reference.target.descriptor == descriptor) { - result.push_back(reference); - } - } - return result; -} - -std::vector ReferenceScan::fieldReferencesFromClass(const std::string& owner) const -{ - std::vector result; - for (const auto& reference : fields_) { - if (reference.site.sourceClass == owner) result.push_back(reference); - } - return result; -} - -std::vector ReferenceScan::typeReferencesTo(const std::string& targetType) const -{ - std::vector result; - for (const auto& reference : types_) { - if (reference.targetType == targetType) result.push_back(reference); - } - return result; -} - -std::vector ReferenceScan::typeReferencesFromClass(const std::string& owner) const -{ - std::vector result; - for (const auto& reference : types_) { - if (reference.site.sourceClass == owner) result.push_back(reference); - } - return result; -} - -std::string methodReferenceKind(MethodReferenceKind) -{ - return "direct"; -} - -std::string fieldReferenceKind(FieldReferenceKind) -{ - return "direct"; -} - -ReferenceInfo referenceInfo(const ReferenceSite& site, const std::string& target, const std::string& kind) -{ - return {site.sourceClass, site.sourceMethod, site.sourceDescriptor, site.insnIndex, target, kind}; -} - -void sortReferences(std::vector& references) -{ - std::sort(references.begin(), references.end(), [](const ReferenceInfo& left, - const ReferenceInfo& right) { - if (left.sourceClass != right.sourceClass) return left.sourceClass < right.sourceClass; - if (left.sourceMethod != right.sourceMethod) return left.sourceMethod < right.sourceMethod; - if (left.sourceDescriptor != right.sourceDescriptor) - return left.sourceDescriptor < right.sourceDescriptor; - if (left.instructionIndex != right.instructionIndex) - return left.instructionIndex < right.instructionIndex; - if (left.kind != right.kind) return left.kind < right.kind; - return left.target < right.target; - }); -} - -} // namespace mjbase::workspace diff --git a/mjbase/src/mjbase/workspace/reference_scan.hpp b/mjbase/src/mjbase/workspace/reference_scan.hpp deleted file mode 100644 index 87b853e..0000000 --- a/mjbase/src/mjbase/workspace/reference_scan.hpp +++ /dev/null @@ -1,128 +0,0 @@ -/// @file reference_scan.hpp -/// @brief 工作区内直写引用扫描:字节码编码事实(方法/字段/类型)。 -/// -/// ReferenceIndex 的直接引用切片:只回答「哪些扫描过的 class 和方法体 -/// 直接引用给定的 method/field/type」。反射事实族中 class-string 形态 -/// 事实已随迁(字符串常量按 remapper 反射语法识别为类型引用候选);常量反射 -/// 成员查找、method handle、lambda bootstrap、别名传播与注解/签名/结构元数据 -/// 类型引用未随迁——那些是独立的共享分析设施,待主线落(见批次报告)。 -/// 本扫描件覆盖:方法调用(invoke 家族)、字段访问(get/put 家族)、显式类型指令 -/// (new/checkcast/instanceof/anewarray/multianewarray)、LDC class 常量、 -/// LDC class-string 反射候选、member owner 类型引用与 class 头声明 -/// (super/interfaces)。 -/// -/// 底座:类字节经 jbc 会话建面,逐方法 promote 后遍历 CodeDom 程序顺序,invoke/ -/// 字段族操作数经 ClassSession::memberOf 解析。site.insnIndex 为方法体内代码指令 -/// 序号;class 级引用(头声明)site 的 sourceMethod 为空且 insnIndex 为 -1。 -#pragma once - -#include -#include - -#include "mjbase/model/resource_container.hpp" -#include "mjbase/workspace/workspace_types.hpp" - -namespace mjbase::workspace { - -/// @brief 稳定的 JVM 方法引用标识(自持拷贝,build 返回后独立于扫描中间态)。 -struct MethodRefKey -{ - std::string owner; - std::string name; - std::string descriptor; -}; - -/// @brief 稳定的 JVM 字段引用标识。 -struct FieldRefKey -{ - std::string owner; - std::string name; - std::string descriptor; -}; - -/// @brief 引用位置(方法级引用带指令序号;class 级引用 method/desc 为空、index 为 -1)。 -struct ReferenceSite -{ - std::string sourceClass; - std::string sourceMethod; - std::string sourceDescriptor; - ca::i32 insnIndex = -1; -}; - -/// @brief 方法引用种类。本切片只产出 Direct(编码事实)。 -enum class MethodReferenceKind : ca::u8 -{ - Direct, -}; - -/// @brief 字段引用种类。本切片只产出 Direct(编码事实)。 -enum class FieldReferenceKind : ca::u8 -{ - Direct, -}; - -/// @brief 一条方法引用事实。 -struct MethodReference -{ - MethodRefKey target; - ReferenceSite site; - MethodReferenceKind kind = MethodReferenceKind::Direct; -}; - -/// @brief 一条字段引用事实。 -struct FieldReference -{ - FieldRefKey target; - ReferenceSite site; - FieldReferenceKind kind = FieldReferenceKind::Direct; -}; - -/// @brief 一条类型引用事实。 -struct TypeReference -{ - std::string targetType; - ReferenceSite site; -}; - -/// @brief 引用扫描结果与查询面(值语义;build 可重复调用全量重建)。 -class ReferenceScan -{ -public: - /// @brief 扫描容器内全部 ClassResource;畸形 class 跳过(容错 CLI 纪律)。 - void build(const model::ResourceContainer& container); - - /// 精确目标查询:编码目标为 owner/name/descriptor 的方法引用。 - std::vector methodReferencesTo(const std::string& owner, const std::string& name, - const std::string& descriptor) const; - /// 来源方法查询:owner.name(descriptor) 方法体发出的全部方法引用。 - std::vector methodReferencesFrom(const std::string& owner, const std::string& name, - const std::string& descriptor) const; - /// 来源类查询:class 声明的全部方法发出的方法引用。 - std::vector methodReferencesFromClass(const std::string& owner) const; - /// 精确目标查询:编码目标为 owner/name/descriptor 的字段访问。 - std::vector fieldReferencesTo(const std::string& owner, const std::string& name, - const std::string& descriptor) const; - /// 来源类查询:class 声明的全部方法发出的字段访问。 - std::vector fieldReferencesFromClass(const std::string& owner) const; - /// 精确目标查询:编码引用 targetType 的全部位置(指令级 + class 头声明)。 - std::vector typeReferencesTo(const std::string& targetType) const; - /// 来源类查询:class 发出的全部类型引用。 - std::vector typeReferencesFromClass(const std::string& owner) const; - -private: - std::vector methods_; - std::vector fields_; - std::vector types_; -}; - -/// @brief MethodReferenceKind → DTO kind 文本(本切片恒 "direct")。 -std::string methodReferenceKind(MethodReferenceKind kind); - -/// @brief FieldReferenceKind → DTO kind 文本(本切片恒 "direct")。 -std::string fieldReferenceKind(FieldReferenceKind kind); - -/// @brief ReferenceInfo 组装与稳定排序(xref 查询共用)。 -ReferenceInfo referenceInfo(const ReferenceSite& site, const std::string& target, const std::string& kind); -void sortReferences(std::vector& references); - -} // namespace mjbase::workspace diff --git a/mjbase/src/mjbase/workspace/summary_scan.cpp b/mjbase/src/mjbase/workspace/summary_scan.cpp deleted file mode 100644 index 7136f89..0000000 --- a/mjbase/src/mjbase/workspace/summary_scan.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "mjbase/workspace/summary_scan.hpp" - -#include "jbc/core/reader.hpp" - -namespace mjbase::workspace { - -WorkspaceResult decodeClassSummary(const std::string& uri, - const model::ClassResource& resource) -{ - auto read = jbc::core::readClass(jbc::core::ByteView(resource.get())); - if (read.is_err()) - return ca::core::Err(WorkspaceErrorInfo{ - WorkspaceError::DataLoss, - "decode class summary " + uri + ": " + - jbc::core::to_string(std::move(read).unwrap_err())}); - auto parsed = std::move(read).unwrap(); - const jbc::core::ClassFile& cf = *parsed.file; - - ClassSummary output; - output.resourceUri = uri; - output.internalName = std::string{cf.thisName()}; - output.superName = std::string{cf.superName()}; - output.access = static_cast(cf.access); - // ASM 版本语义:version = major | (minor << 16)。 - output.version = static_cast(cf.major) | (static_cast(cf.minor) << 16); - output.interfaceCount = cf.interfaces.size(); - output.fieldCount = cf.fields.size(); - output.methodCount = cf.methods.size(); - return ca::core::Ok(std::move(output)); -} - -} // namespace mjbase::workspace diff --git a/mjbase/src/mjbase/workspace/summary_scan.hpp b/mjbase/src/mjbase/workspace/summary_scan.hpp deleted file mode 100644 index e258162..0000000 --- a/mjbase/src/mjbase/workspace/summary_scan.hpp +++ /dev/null @@ -1,25 +0,0 @@ -/// @file summary_scan.hpp -/// @brief ClassSummary 扫描入口(libjbc 后端)。 -/// -/// 字段级对拍(对拍即回归门); -/// mjbase 只有 libjbc 一个底座,对拍面随之收敛为单后端字段契约: -/// 对同一 (uri, class 字节) 产出 internalName/superName/access/version/ -/// interfaceCount/fieldCount/methodCount 字段(resourceUri 原样回填)。 -/// 失败路径统一走 DataLoss。 -#pragma once - -#include - -#include "mjbase/model/resource_model.hpp" -#include "mjbase/workspace/workspace_types.hpp" - -namespace mjbase::workspace { - -/// @brief libjbc 后端:骨架扫描(不解任何属性体,preserveSourceBytes=false)。 -/// @param uri 资源 URI(原样填入 ClassSummary::resourceUri)。 -/// @param resource class 字节资源。 -/// @return ClassSummary;readClass 失败转 DataLoss。 -WorkspaceResult decodeClassSummary(const std::string& uri, - const model::ClassResource& resource); - -} // namespace mjbase::workspace diff --git a/mjbase/src/mjbase/workspace/workspace.cpp b/mjbase/src/mjbase/workspace/workspace.cpp deleted file mode 100644 index b7ff695..0000000 --- a/mjbase/src/mjbase/workspace/workspace.cpp +++ /dev/null @@ -1,4806 +0,0 @@ -#include "mjbase/workspace/workspace.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "jbc/core/attr_codec.hpp" -#include "jbc/core/insn.hpp" -#include "jbc/core/opcode.hpp" -#include "jbc/core/reader.hpp" -#include "jbc/ir/units.hpp" - -#include "mjbase/hierarchy/class_hierarchy.hpp" -#include "mjbase/hierarchy/summary.hpp" -#include "mjbase/analysis/class_scan.hpp" -#include "mjbase/asm/remapper/member_remap_plan.hpp" -#include "mjbase/asm/remapper/resource_remapper.hpp" -#include "mjbase/jar/jar_reader.hpp" -#include "mjbase/jar/jar_util.hpp" -#include "mjbase/jasm/classfile_utf8.hpp" -#include "mjbase/jasm/jvm_jasm_service.hpp" -#include "mjbase/jasm/resource_jasm_service.hpp" -#include "mjbase/keys.hpp" -#include "mjbase/mapping/identifier_util.hpp" -#include "mjbase/model/resource_container.hpp" -#include "mjbase/model/resource_helper.hpp" -#include "mjbase/model/resource_model.hpp" -#include "mjbase/project/edit_ledger.hpp" -#include "mjbase/project/edit_ledger_store.hpp" -#include "mjbase/project/project_store.hpp" -#include "mjbase/spi/class_file_sanitize.hpp" -#include "mjbase/spi/common_super_resolver.hpp" -#include "mjbase/spi/job_context.hpp" -#include "mjbase/spi/pass_manager.hpp" -#include "mjbase/spi/pass_name_registry.hpp" -#include "mjbase/spi/pass_pipeline.hpp" -#include "mjbase/spi/pass_registry.hpp" -#include "mjbase/spi/pipeline_builder.hpp" -#include "mjbase/spi/strict_type_resolver.hpp" -#include "mjbase/workspace/class_mutation_service.hpp" -#include "mjbase/workspace/classfile_support.hpp" -#include "mjbase/workspace/reference_scan.hpp" -#include "mjbase/workspace/summary_scan.hpp" - -namespace mjbase::workspace { - -namespace { - -using support::utf8Text; - -// ── 基础助手 ───────────────────────────────────────────────────── - -WorkspaceErrorInfo error(WorkspaceError code, const std::string& operation, - const std::string& detail) -{ - return WorkspaceErrorInfo{code, operation + ": " + detail}; -} - -ca::str::Utf16String utf16(const std::string& value) -{ - return ca::str::Utf16String::from_utf8_string(ca::str::Utf8StringRef::from_data( - reinterpret_cast(value.data()), value.size())); -} - -/// @brief UTF-8 窄串 → filesystem path(Windows 上 path(std::string) 按 ACP -/// 解码,非 ASCII 必乱;与 model/resource_io.cpp 的 u8path 口径一致)。 -std::filesystem::path fsPath(const std::string& text) -{ - return std::filesystem::u8path(text); -} - -/// @brief filesystem path → UTF-8 窄串(path::string() 在 Windows 走 ACP, -/// 非 ASCII 会抛 code-page 异常;API 边界契约是 UTF-8)。 -std::string pathToUtf8(const std::filesystem::path& path) -{ - const auto u8 = path.u8string(); - return std::string(reinterpret_cast(u8.c_str()), u8.size()); -} - -/// @brief IResource::getLocation() 借用视图 → owning 文本(跨界传递用)。 -std::string locationText(const model::IResource& resource) -{ - const auto location = resource.getLocation(); - return std::string(reinterpret_cast(location.data()), location.byte_length()); -} - -/// @brief 资源类型短名(小写口径:"class"/"manifest"/"jar"/"resource")。 -std::string resourceTypeName(model::ResourceType type) -{ - switch (type) { - case model::ResourceType::Class: return "class"; - case model::ResourceType::Manifest: return "manifest"; - case model::ResourceType::Jar: return "jar"; - default: return "resource"; - } -} - -/// @brief model 模块错误 → workspace 错误值。 -/// -/// 值语义下按码分流: -/// 文件/条目缺失 → NotFound,zip-slip → InvalidArgument,其余(Io/BadEntry/ -/// UnsupportedFormat/TypeMismatch/BadArgument)保持 DataLoss 兜底。 -WorkspaceErrorInfo modelFailure(const std::string& operation, model::ModelError modelError) -{ - switch (modelError) { - case model::ModelError::NotFound: - return error(WorkspaceError::NotFound, operation, "archive or entry not found"); - case model::ModelError::PathEscape: - return error(WorkspaceError::InvalidArgument, operation, - "archive entry escapes target dir"); - default: - return error(WorkspaceError::DataLoss, operation, model::to_string(modelError)); - } -} - -WorkspaceResult> bytesOf(const model::IResource& resource) -{ - if (const auto* classResource = - ca::core::tag_cast::dyn_cast(&resource)) - return ca::core::Ok(classResource->get()); - if (const auto* manifestResource = - ca::core::tag_cast::dyn_cast(&resource)) - return ca::core::Ok(manifestResource->get().toBytes()); - if (const auto* unknownResource = - ca::core::tag_cast::dyn_cast(&resource)) - return ca::core::Ok(unknownResource->get()); - return ca::core::Err(error(WorkspaceError::Unimplemented, "read resource", - "unsupported resource type at " + locationText(resource))); -} - -std::unique_ptr cloneResource(const model::IResource& resource) -{ - if (const auto* classResource = - ca::core::tag_cast::dyn_cast(&resource)) - return std::make_unique(locationText(*classResource), - classResource->get()); - if (const auto* manifestResource = - ca::core::tag_cast::dyn_cast(&resource)) - return std::make_unique(manifestResource->get()); - if (const auto* unknownResource = - ca::core::tag_cast::dyn_cast(&resource)) - return std::make_unique(locationText(*unknownResource), - unknownResource->get()); - return nullptr; -} - -WorkspaceResult> cloneContainer( - const model::ResourceContainer& source) -{ - auto result = std::make_unique(); - WorkspaceErrorInfo err; - source.visitResources([&](std::string_view uri, const model::IResource& resource) { - if (err.is_err()) return; - auto copied = cloneResource(resource); - if (!copied) { - err = error(WorkspaceError::Unimplemented, "clone container", - "unsupported resource type at " + std::string(uri)); - return; - } - result->addResource(uri, std::move(copied)); - }); - if (err.is_err()) return ca::core::Err(std::move(err)); - return ca::core::Ok(std::move(result)); -} - -// ── class 解码(libjbc 会话;字段口径照旧)────────────────────────────── - -WorkspaceResult> sessionOf( - const std::vector& bytes, const std::string& operation) -{ - auto parsed = jbc::ir::makeSessionFromBytes(jbc::core::ByteView(bytes)); - if (parsed.is_err()) - return ca::core::Err( - error(WorkspaceError::DataLoss, operation, "class bytes cannot be parsed")); - return ca::core::Ok(std::move(parsed).unwrap()); -} - -/// @brief Code 嵌套 LNT 行 → {line, 代码指令序号};pc 无对应指令时 index 为 -1。 -WorkspaceResult> lineNumbersOf(jbc::ir::ClassSession& session, - jbc::core::MemberInfo& method, - ca::usize methodIndex) -{ - std::vector result; - jbc::core::CodeAttr* code = support::ensureCode(session.backing(), method); - if (code == nullptr) return ca::core::Ok(std::move(result)); - - std::vector> rows; // {pc, line} - for (jbc::core::AttrSlot& nested : code->attrs) { - if (!session.backing().cp.utf8Equals(nested.nameCp, "LineNumberTable")) continue; - if (!nested.decoded()) { - if (session.backing().decodeAttr(nested).is_err()) continue; - } - const auto* table = - ca::core::tag_cast::cast(nested.peek()); - if (table == nullptr) continue; - for (const auto& row : table->rows) - rows.emplace_back(row.startPc, static_cast(row.line)); - } - if (rows.empty()) return ca::core::Ok(std::move(result)); - - auto promoted = jbc::ir::promoteMethod(session, methodIndex); - if (promoted.is_err()) - return ca::core::Err( - error(WorkspaceError::DataLoss, "decode method", "method body cannot be promoted")); - auto raw = promoted.unwrap()->dom.exportRaw(); - if (raw.is_err()) - return ca::core::Err( - error(WorkspaceError::DataLoss, "decode method", "method body cannot be exported")); - const auto insns = std::move(raw).unwrap(); - for (const auto& [pc, line] : rows) { - LineNumberInfo info; - info.line = line; - for (ca::usize index = 0; index < insns.size(); ++index) { - if (insns[index].pc == pc) { - info.instructionIndex = static_cast(index); - break; - } - } - result.push_back(info); - } - return ca::core::Ok(std::move(result)); -} - -FieldInfo makeFieldInfo(jbc::core::ClassFile& cf, jbc::core::MemberInfo& field) -{ - FieldInfo output; - output.name = utf8Text(cf, field.nameCp); - output.descriptor = utf8Text(cf, field.descCp); - output.signature = support::signatureText(cf, field.attrs); - output.access = static_cast(field.access); - output.constantValue = support::fieldConstantValue(cf, field); - support::collectAnnotations(cf, field.attrs, output.annotations); - return output; -} - -WorkspaceResult makeMethodInfo(jbc::ir::ClassSession& session, - jbc::core::MemberInfo& method, ca::usize methodIndex) -{ - jbc::core::ClassFile& cf = session.backing(); - MethodInfo output; - output.name = utf8Text(cf, method.nameCp); - output.descriptor = utf8Text(cf, method.descCp); - output.signature = support::signatureText(cf, method.attrs); - output.access = static_cast(method.access); - if (jbc::core::CodeAttr* code = support::ensureCode(cf, method)) { - output.maxStack = static_cast(code->maxStack); - output.maxLocals = static_cast(code->maxLocals); - output.tryCatchCount = code->excTable.size(); - // instructionCount:代码指令序数(CodeDom 程序顺序;label/line 不是指令)。 - auto promoted = jbc::ir::promoteMethod(session, methodIndex); - if (promoted.is_err()) - return ca::core::Err(error( - WorkspaceError::DataLoss, "decode method", - "method body cannot be promoted: " + utf8Text(cf, method.nameCp) + " (" + - std::to_string(static_cast(promoted.unwrap_err())) + ")")); - output.instructionCount = promoted.unwrap()->dom.size(); - support::collectLocalVariables(cf, *code, output.localVariables); - } - output.exceptions = support::exceptionNames(cf, method); - auto lines = lineNumbersOf(session, method, methodIndex); - if (lines.is_err()) return ca::core::Err(std::move(lines).unwrap_err()); - output.lineNumbers = std::move(lines).unwrap(); - support::collectAnnotations(cf, method.attrs, output.annotations); - return ca::core::Ok(std::move(output)); -} - -/// @brief InnerClasses 属性的内部类名(去重排序;inner_name_index=0 的匿名形态跳过)。 -/// -/// typed InnerClassesAttr 体经公开解码入口读出(B3 分派接线前后同形,不触碰 -/// 槽状态);解码失败的槽跳过(查询面对畸形输入宽容)。 -std::vector innerClassNames(jbc::core::ClassFile& cf) -{ - std::vector names; - for (jbc::core::AttrSlot& slot : cf.attrs) { - if (!cf.cp.utf8Equals(slot.nameCp, "InnerClasses")) continue; - auto d = decodeInnerClassesBody(cf, slot); - if (d.is_err()) continue; - const auto body = std::move(d).unwrap(); - const auto& ic = static_cast(*body); - for (const jbc::core::InnerClassesAttr::Row& row : ic.rows) { - if (row.innerNameCp == 0) continue; - const auto name = utf8Text(cf, row.innerNameCp); - if (!name.empty()) names.push_back(name); - } - } - std::sort(names.begin(), names.end()); - names.erase(std::unique(names.begin(), names.end()), names.end()); - return names; -} - -WorkspaceResult decodeClass(const std::string& uri, const model::ClassResource& resource) -{ - auto session = sessionOf(resource.get(), "decode class " + uri); - if (session.is_err()) return ca::core::Err(std::move(session).unwrap_err()); - auto held = std::move(session).unwrap(); - jbc::core::ClassFile& cf = held->backing(); - - ClassInfo output; - output.resourceUri = uri; - // cp modified UTF-8 → 规范形 UTF-8:内名/父类名与容器 URI、参数面同形, - // 增补平面类名(cp 内代理对形)不解码会与 UTF-8 形选择器比对 miss。 - output.internalName = mjbase::jasm::classFileUtf8ToString(cf.thisName()); - output.superName = mjbase::jasm::classFileUtf8ToString(cf.superName()); - output.signature = support::signatureText(cf, cf.attrs); - output.access = static_cast(cf.access); - // version = major | (minor << 16)。 - output.version = static_cast(cf.major) | (static_cast(cf.minor) << 16); - for (const auto interfaceCp : cf.interfaces) { - output.interfaces.push_back(std::string{cf.className(interfaceCp)}); - } - for (auto& field : cf.fields) output.fields.push_back(makeFieldInfo(cf, field)); - for (ca::usize index = 0; index < cf.methods.size(); ++index) { - auto info = makeMethodInfo(*held, cf.methods[index], index); - if (info.is_err()) return ca::core::Err(std::move(info).unwrap_err()); - output.methods.push_back(std::move(info).unwrap()); - } - output.innerClasses = innerClassNames(cf); - support::collectAnnotations(cf, cf.attrs, output.annotations); - return ca::core::Ok(std::move(output)); -} - -// ── 搜索 ──────────────────────────────────────────────────────── - -std::string lowercase(std::string value) -{ - std::transform(value.begin(), value.end(), value.begin(), [](unsigned char character) { - return static_cast(std::tolower(character)); - }); - return value; -} - -bool matchesText(const std::string& value, const std::string& query, - const SearchOptions& options, const std::regex* compiledRegex) -{ - if (options.matchMode == SearchMatchMode::Regex) - return compiledRegex != nullptr && std::regex_search(value, *compiledRegex); - const auto comparableValue = options.caseSensitive ? value : lowercase(value); - const auto comparableQuery = options.caseSensitive ? query : lowercase(query); - if (options.matchMode == SearchMatchMode::Exact) return comparableValue == comparableQuery; - return comparableValue.find(comparableQuery) != std::string::npos; -} - -WorkspaceErrorInfo validateSearchOptions(const std::string& query, const SearchOptions& options, - const std::string& operation) -{ - if (query.empty()) - return error(WorkspaceError::InvalidArgument, operation, "query must not be empty"); - if (query.size() > 1024) - return error(WorkspaceError::InvalidArgument, operation, "query exceeds 1024 bytes"); - if (!options.memberKind.empty() && options.memberKind != "field" && - options.memberKind != "method") - return error(WorkspaceError::InvalidArgument, operation, - "member kind must be field or method"); - if (options.opcode < -1 || options.opcode >= 200) - return error(WorkspaceError::InvalidArgument, operation, - "opcode must be between 0 and 199"); - return WorkspaceErrorInfo{}; -} - -WorkspaceResult> compileSearchRegex(const std::string& query, - const SearchOptions& options, - const std::string& operation) -{ - WorkspaceErrorInfo status = validateSearchOptions(query, options, operation); - if (status.is_err()) return ca::core::Err(std::move(status)); - if (options.matchMode != SearchMatchMode::Regex) - return ca::core::Ok(std::optional{}); - try { - auto flags = std::regex_constants::ECMAScript; - if (!options.caseSensitive) flags |= std::regex_constants::icase; - return ca::core::Ok(std::optional(std::in_place, query, flags)); - } - catch (const std::regex_error& exception) { - return ca::core::Err(error(WorkspaceError::InvalidArgument, operation, exception.what())); - } -} - -bool matchesSearchScope(const std::string& owner, const SearchOptions& options) -{ - if (!options.owner.empty() && owner != options.owner) return false; - return options.packagePrefix.empty() || owner.rfind(options.packagePrefix, 0) == 0; -} - -bool matchesAnyText(const std::vector& values, const std::string& query, - const SearchOptions& options, const std::regex* compiledRegex) -{ - return std::any_of(values.begin(), values.end(), [&](const std::string& value) { - return matchesText(value, query, options, compiledRegex); - }); -} - -template -std::string numberText(T value) -{ - std::ostringstream output; - if constexpr (std::is_floating_point_v) { - output << std::setprecision(std::numeric_limits::max_digits10); - } - output << value; - return output.str(); -} - -bool matchesNumber(const std::string& value, const std::string& query) -{ - if (value == query) return true; - try { - std::size_t valueOffset = 0; - std::size_t queryOffset = 0; - const auto parsedValue = std::stold(value, &valueOffset); - const auto parsedQuery = std::stold(query, &queryOffset); - return valueOffset == value.size() && queryOffset == query.size() && - parsedValue == parsedQuery; - } - catch (const std::exception&) { - return false; - } -} - -WorkspaceErrorInfo validateNumberQuery(const std::string& query) -{ - try { - std::size_t offset = 0; - const auto parsed = std::stold(query, &offset); - (void)parsed; - if (offset == query.size()) return WorkspaceErrorInfo{}; - } - catch (const std::exception&) { - } - return error(WorkspaceError::InvalidArgument, "search numbers", - "query must be a complete numeric value"); -} - -void sortAndDeduplicateSearchHits(std::vector& hits) -{ - std::sort(hits.begin(), hits.end(), [](const SearchHit& left, const SearchHit& right) { - return std::tie(left.resourceUri, - left.owner, - left.name, - left.descriptor, - left.instructionIndex, - left.kind, - left.value) < - std::tie(right.resourceUri, - right.owner, - right.name, - right.descriptor, - right.instructionIndex, - right.kind, - right.value); - }); - hits.erase( - std::unique(hits.begin(), hits.end(), [](const SearchHit& left, const SearchHit& right) { - return left.resourceUri == right.resourceUri && left.kind == right.kind && - left.owner == right.owner && left.name == right.name && - left.descriptor == right.descriptor && - left.instructionIndex == right.instructionIndex && left.value == right.value; - }), - hits.end()); -} - -// ── 指令渲染(libjbc 形状分派;node_type 按指令语义命名)──────────────── - -/// @brief libjbc 无 ASM 节点类型 → 按指令语义命名(invoke/field/ldc/cp_ref/ -/// jump/var/const/array/iinc/switch 族);标签/行号不是指令,操作数 -/// 与跳转以 jasm 全文为权威。旧「按操作数布局」命名把全部 CP 形态 -/// (getstatic/invoke*/checkcast/new…)标成 ldc,搜索结果的类型标签 -/// 失去判别力,改为助记符归类。 -std::string instructionNodeType(jbc::core::Op op) -{ - const char* mnemonic = jbc::core::mnemonicOf(op); - const std::string name = mnemonic != nullptr ? mnemonic : ""; - - const auto isAnyOf = [&name](std::initializer_list candidates) { - return std::any_of(candidates.begin(), candidates.end(), [&](std::string_view item) { - return name == item; - }); - }; - if (name == "invokedynamic") return "invokedynamic"; - if (name.rfind("invoke", 0) == 0) return "invoke"; - if (isAnyOf({"getfield", "getstatic", "putfield", "putstatic"})) return "field"; - if (isAnyOf({"ldc", "ldc_w", "ldc2_w"})) return "ldc"; - if (isAnyOf({"new", "anewarray", "checkcast", "instanceof"})) return "cp_ref"; - if (name == "multianewarray") return "multianewarray"; - if (name == "iinc") return "iinc"; - //数组元素加载/存储(两字符类型前缀 iaload/aastore 等):先于局部变量 - //load/store 判定——裸名 aload/astore 属数组族;带槽位的 aload_N/astore_N - //全名不命中数组表,剥后缀后走局部变量分支。 - static constexpr std::array kArrayOps = { - "iaload", "laload", "faload", "daload", "aaload", "baload", "caload", "saload", - "iastore", "lastore", "fastore", "dastore", "aastore", "bastore", "castore", - "sastore"}; - for (const auto& arrayOp : kArrayOps) { - if (name == arrayOp) return "array"; - } - //局部变量加载/存储族:类型前缀(i/l/f/d/a)+ load/store;带槽位的短格式 - //(aload_0 等)先剥掉数字后缀再匹配。 - auto baseName = name; - const auto underscore = name.rfind('_'); - if (underscore != std::string::npos && - std::all_of(name.begin() + static_cast(underscore) + 1, name.end(), - [](unsigned char c) { return std::isdigit(c) != 0; })) - baseName = name.substr(0, underscore); - if (baseName.size() == 5 && baseName.compare(1, 4, "load") == 0 && - std::string("ilfda").find(baseName[0]) != std::string::npos) - return "var"; - if (baseName.size() == 6 && baseName.compare(1, 5, "store") == 0 && - std::string("ilfda").find(baseName[0]) != std::string::npos) - return "var"; - if (name == "ret") return "var"; - if (name.rfind("iconst", 0) == 0 || name.rfind("lconst", 0) == 0 || - name.rfind("fconst", 0) == 0 || name.rfind("dconst", 0) == 0 || - isAnyOf({"aconst_null", "bipush", "sipush"})) - return "const"; - switch (jbc::core::shapeOf(op)) { - case jbc::core::Shape::Branch2: - case jbc::core::Shape::Branch4: return "jump"; - case jbc::core::Shape::TableSwitch: return "tableswitch"; - case jbc::core::Shape::LookupSwitch: return "lookupswitch"; - case jbc::core::Shape::None: return "insn"; - case jbc::core::Shape::Imm1: - case jbc::core::Shape::Imm2: - case jbc::core::Shape::Atype1: return "int"; - case jbc::core::Shape::Local1: return "var"; - case jbc::core::Shape::Iinc1: return "iinc"; - default: break; - } - return "insn"; -} - -/// @brief 成员引用 CP 索引 → "owner.name:desc"(operandComment 渲染口径)。 -std::string memberRefColonText(const jbc::core::ClassFile& cf, ca::u32 cpIndex) -{ - if (!cf.cp.valid(cpIndex)) return {}; - const jbc::core::ConstantPool::Entry& entry = cf.cp.at(cpIndex); - if (entry.tag != jbc::core::CpTag::Fieldref && entry.tag != jbc::core::CpTag::Methodref && - entry.tag != jbc::core::CpTag::InterfaceMethodref) - return {}; - std::string owner; - std::string name; - std::string desc; - if (cf.cp.valid(entry.a)) { - const auto& classEntry = cf.cp.at(entry.a); - if (classEntry.tag == jbc::core::CpTag::Class) owner = utf8Text(cf, classEntry.a); - } - if (cf.cp.valid(entry.b)) { - const auto& nt = cf.cp.at(entry.b); - if (nt.tag == jbc::core::CpTag::NameAndType) { - name = utf8Text(cf, nt.a); - desc = utf8Text(cf, nt.b); - } - } - return owner + "." + name + ":" + desc; -} - -/// @brief 成员引用 CP 索引 → 无冒号拼接(指令搜索文本口径:workspace/Subject.value()I)。 -std::string memberRefFlatText(const jbc::core::ClassFile& cf, ca::u32 cpIndex) -{ - const std::string resolved = memberRefColonText(cf, cpIndex); - if (resolved.empty()) return {}; - const auto colon = resolved.rfind(':'); - return resolved.substr(0, colon) + resolved.substr(colon + 1); -} - -/// @brief LDC 常量与成员引用的可读注释(字符串加引号、数值无后缀、已过 -/// IdentifierUtil 可读化——InstructionDetailInfo::operandComment 契约)。 -std::string ldcComment(const jbc::core::ClassFile& cf, ca::u32 cpIndex) -{ - if (!cf.cp.valid(cpIndex)) return {}; - const jbc::core::ConstantPool::Entry& entry = cf.cp.at(cpIndex); - char tmp[64]; - switch (entry.tag) { - case jbc::core::CpTag::String: - return "\"" + - mjbase::mapping::IdentifierUtil::printReadable(utf8Text(cf, entry.a)) + "\""; - case jbc::core::CpTag::Class: - return mjbase::mapping::IdentifierUtil::printReadable(utf8Text(cf, entry.a)); - case jbc::core::CpTag::Integer: - std::snprintf(tmp, sizeof(tmp), "%d", entry.ival); - return tmp; - case jbc::core::CpTag::Float: - std::snprintf(tmp, sizeof(tmp), "%g", - static_cast(support::bitsToFloat(static_cast(entry.ival)))); - return tmp; - case jbc::core::CpTag::Long: - std::snprintf(tmp, sizeof(tmp), "%lld", static_cast(entry.lval)); - return tmp; - case jbc::core::CpTag::Double: - std::snprintf(tmp, sizeof(tmp), "%g", - support::bitsToDouble(static_cast(entry.lval))); - return tmp; - case jbc::core::CpTag::Fieldref: - case jbc::core::CpTag::Methodref: - case jbc::core::CpTag::InterfaceMethodref: - { - // owner.name:desc 各段过 IdentifierUtil 可读化。 - std::string owner; - std::string name; - std::string desc; - if (cf.cp.valid(entry.a)) { - const auto& classEntry = cf.cp.at(entry.a); - if (classEntry.tag == jbc::core::CpTag::Class) - owner = mjbase::mapping::IdentifierUtil::printReadable(utf8Text(cf, classEntry.a)); - } - if (cf.cp.valid(entry.b)) { - const auto& nt = cf.cp.at(entry.b); - if (nt.tag == jbc::core::CpTag::NameAndType) { - name = mjbase::mapping::IdentifierUtil::printReadable(utf8Text(cf, nt.a)); - desc = mjbase::mapping::IdentifierUtil::printReadable(utf8Text(cf, nt.b)); - } - } - return owner + "." + name + ":" + desc; - } - default: return {}; - } -} - -/// @brief 一条指令的完整渲染(保留 CP 索引与字节 PC;switch 给绝对目标 PC)。 -InstructionDetailInfo renderInstruction(const jbc::core::ClassFile& cf, - const jbc::core::RawInsn& insn) -{ - InstructionDetailInfo info; - info.pc = static_cast(insn.pc); - info.opcode = static_cast(insn.op); - const char* mnemonic = jbc::core::mnemonicOf(insn.op); - info.mnemonic = mnemonic != nullptr ? mnemonic : "?"; - char tmp[64]; - switch (jbc::core::shapeOf(insn.op)) { - case jbc::core::Shape::CP1: - case jbc::core::Shape::CP2: - std::snprintf(tmp, sizeof(tmp), "#%u", insn.cp); - info.operandText = tmp; - info.cpIndex = static_cast(insn.cp); - break; - case jbc::core::Shape::Imm1: - case jbc::core::Shape::Imm2: - case jbc::core::Shape::Atype1: - case jbc::core::Shape::Local1: - std::snprintf(tmp, sizeof(tmp), "%d", insn.a); - info.operandText = tmp; - break; - case jbc::core::Shape::Iinc1: - std::snprintf(tmp, sizeof(tmp), "%d, %d", insn.a, insn.b); - info.operandText = tmp; - break; - case jbc::core::Shape::CP2Dims: - std::snprintf(tmp, sizeof(tmp), "#%u dims=%d", insn.cp, insn.a); - info.operandText = tmp; - info.cpIndex = static_cast(insn.cp); - break; - case jbc::core::Shape::CP2Count: - case jbc::core::Shape::CP2Zero2: - std::snprintf(tmp, sizeof(tmp), "#%u", insn.cp); - info.operandText = tmp; - info.cpIndex = static_cast(insn.cp); - break; - case jbc::core::Shape::TableSwitch: - { - info.isSwitch = true; - info.switchDefaultPc = static_cast(insn.pc + static_cast(insn.t)); - for (ca::usize k = 0; k < insn.offs.size(); ++k) { - info.switchCases.push_back( - {insn.a + static_cast(k), - static_cast(insn.pc + static_cast(insn.offs[k]))}); - } - break; - } - case jbc::core::Shape::LookupSwitch: - { - info.isSwitch = true; - info.switchDefaultPc = static_cast(insn.pc + static_cast(insn.t)); - for (const auto& kase : insn.cases) { - info.switchCases.push_back( - {kase.key, static_cast(insn.pc + static_cast(kase.off))}); - } - break; - } - case jbc::core::Shape::None: - case jbc::core::Shape::Branch2: - case jbc::core::Shape::Branch4: break; - } - return info; -} - -// ── 字节 diff / 修复 / 指纹 ────────────────────────────────────── - -std::vector diffContainers(const model::ResourceContainer& before, - const model::ResourceContainer& after) -{ - std::set uris; - before.visitResources( - [&](std::string_view uri, const model::IResource&) { uris.insert(std::string(uri)); }); - after.visitResources( - [&](std::string_view uri, const model::IResource&) { uris.insert(std::string(uri)); }); - - std::vector diffs; - for (const auto& uri : uris) { - const model::IResource* beforeResource = before.getResource(uri); - const model::IResource* afterResource = after.getResource(uri); - std::vector beforeBytes; - std::vector afterBytes; - if (beforeResource != nullptr) { - auto bytes = bytesOf(*beforeResource); - if (bytes.is_err()) continue; - beforeBytes = std::move(bytes).unwrap(); - } - if (afterResource != nullptr) { - auto bytes = bytesOf(*afterResource); - if (bytes.is_err()) continue; - afterBytes = std::move(bytes).unwrap(); - } - if (beforeBytes == afterBytes) continue; - - // 单一公共前后缀收缩:第一个差异字节到最后一个差异字节(照旧口径)。 - ca::usize start = 0; - while (start < beforeBytes.size() && start < afterBytes.size() && - beforeBytes[start] == afterBytes[start]) { - ++start; - } - ca::usize beforeEnd = beforeBytes.size(); - ca::usize afterEnd = afterBytes.size(); - while (beforeEnd > start && afterEnd > start && - beforeBytes[beforeEnd - 1] == afterBytes[afterEnd - 1]) { - --beforeEnd; - --afterEnd; - } - ByteDiff diff; - diff.uri = uri; - diff.beforeSize = beforeBytes.size(); - diff.afterSize = afterBytes.size(); - diff.ranges.push_back({start, beforeEnd - start, afterEnd - start}); - diffs.push_back(std::move(diff)); - } - return diffs; -} - -/// @brief 编辑校验门的作用域:前后差异条目的 URI 集——操作受影响面。 -/// 容器基线未编辑类不参与门禁(它们本来就是这个样子,编辑前就存在)。 -std::vector changedUris(const std::vector& diffs) -{ - std::vector uris; - uris.reserve(diffs.size()); - for (const auto& diff : diffs) uris.push_back(diff.uri); - return uris; -} - -bool hasClassExtension(const std::string& uri) -{ - constexpr char kExtension[] = ".class"; - if (uri.size() < sizeof(kExtension) - 1) return false; - const auto offset = uri.size() - (sizeof(kExtension) - 1); - for (ca::usize index = 0; index < sizeof(kExtension) - 1; ++index) { - const auto character = static_cast(uri[offset + index]); - if (std::tolower(character) != kExtension[index]) return false; - } - return true; -} - -// 内名推导(模型层族视图原语,Multi-Release 版本化 URI 剥离口径见 -// model/resource_container.hpp):本文件不再持有副本。 - -/// @brief class 字节 → 内部名(骨架解析;失败为空)。 -/// -/// 返回规范形 UTF-8:与容器 URI、workspace 索引与参数面同形(类索引用它 -/// 建键,resolveClassUri 用它比对选择器)。 -std::string internalNameOfBytes(const std::vector& bytes) -{ - auto read = jbc::core::readClass(jbc::core::ByteView(bytes)); - if (read.is_err()) return {}; - auto parsed = std::move(read).unwrap(); - return mjbase::jasm::classFileUtf8ToString(parsed.file->thisName()); -} - -std::vector scanJarRepairIssues(const model::ResourceContainer& container, - const JarRepairRequest& request, - ca::usize& classCount) -{ - jasm::JvmJasmService validator; - std::vector issues; - container.visitResources([&](std::string_view uriView, const model::IResource& resource) { - const std::string uri{uriView}; - const auto* classResource = - ca::core::tag_cast::dyn_cast(&resource); - if (classResource != nullptr) { - ++classCount; - const auto validated = validator.validate(classResource->get()); - if (!validated.isOk()) { - issues.push_back({uri, - "invalid_class", - "class bytes did not pass Jasm validation", - classResource->get().size(), - request.removeInvalidClassEntries}); - } - } - else if (hasClassExtension(uri)) { - auto bytes = bytesOf(resource); - issues.push_back({uri, - "invalid_class_entry", - "a .class entry was not recognized as valid class bytes", - bytes.is_ok() ? bytes.unwrap().size() : 0, - request.removeInvalidClassEntries}); - } - if (uri.find("corrupt_output_stream") != std::string::npos) { - auto bytes = bytesOf(resource); - issues.push_back({uri, - "corrupt_output_marker", - "resource name matches a known corrupt output marker", - bytes.is_ok() ? bytes.unwrap().size() : 0, - request.removeCorruptOutputMarkers}); - } - }); - std::sort(issues.begin(), issues.end(), - [](const JarRepairIssue& left, const JarRepairIssue& right) { - if (left.uri != right.uri) return left.uri < right.uri; - return left.kind < right.kind; - }); - return issues; -} - -void removeSelectedRepairIssues(model::ResourceContainer& candidate, - const std::vector& issues) -{ - // 容器以 string_view 键视图指向资源自持文本:先拷贝待删键再删除。 - auto& resources = candidate.getResources(); - std::vector doomed; - for (const auto& issue : issues) { - if (issue.selectedForRemoval) doomed.push_back(issue.uri); - } - for (const std::string& uri : doomed) resources.erase(std::string_view(uri)); -} - -bool hasSelectedRepairIssues(const std::vector& issues) -{ - return std::any_of(issues.begin(), issues.end(), [](const JarRepairIssue& issue) { - return issue.selectedForRemoval; - }); -} - -std::vector changedClassNames(const model::ResourceContainer& container, - const std::vector& diffs) -{ - std::vector result; - for (const auto& diff : diffs) { - const auto* resource = ca::core::tag_cast::dyn_cast( - container.getResource(diff.uri)); - if (resource == nullptr) continue; - const auto name = internalNameOfBytes(resource->get()); - if (!name.empty()) result.push_back(name); - } - std::sort(result.begin(), result.end()); - result.erase(std::unique(result.begin(), result.end()), result.end()); - return result; -} - -void appendUniqueImpact(std::vector& impacts, - std::unordered_set& seen, const std::string& kind, - const std::string& target) -{ - const std::string key = kind + "\n" + target; - if (!seen.insert(key).second) return; - impacts.push_back({kind, target}); -} - -/// @brief 受影响引用事实(mjbase ReferenceScan 直连面:direct 方法/字段/类型引用)。 -std::vector collectImpacts(const model::ResourceContainer& container, - const std::vector& classes) -{ - try { - ReferenceScan index; - index.build(container); - std::vector impacts; - std::unordered_set seen; - for (const auto& owner : classes) { - for (const auto& reference : index.methodReferencesFromClass(owner)) { - appendUniqueImpact(impacts, seen, "method", - reference.target.owner + "." + reference.target.name + - reference.target.descriptor); - } - for (const auto& reference : index.fieldReferencesFromClass(owner)) { - appendUniqueImpact(impacts, seen, "field", - reference.target.owner + "." + reference.target.name + - reference.target.descriptor); - } - for (const auto& reference : index.typeReferencesFromClass(owner)) { - appendUniqueImpact(impacts, seen, "type", reference.targetType); - } - } - std::sort(impacts.begin(), impacts.end(), - [](const ReferenceImpact& left, const ReferenceImpact& right) { - if (left.kind != right.kind) return left.kind < right.kind; - return left.target < right.target; - }); - return impacts; - } - catch (const std::exception&) { - return {}; - } -} - -// ── 指纹(fnv1a64:16hex;长度按小端逐字节入串)───────────────── - -void hashFingerprintBytes(ca::u64& state, const ca::u8* data, ca::usize size) -{ - constexpr ca::u64 kPrime = 1099511628211ULL; - for (ca::usize index = 0; index < size; ++index) { - state ^= data[index]; - state *= kPrime; - } -} - -void hashFingerprintLength(ca::u64& state, ca::usize value) -{ - const auto stableValue = static_cast(value); - for (ca::usize index = 0; index < sizeof(stableValue); ++index) { - const auto byte = static_cast((stableValue >> (index * 8U)) & 0xffU); - hashFingerprintBytes(state, &byte, 1); - } -} - -std::string fingerprintBytes(const std::vector& bytes) -{ - constexpr ca::u64 kOffsetBasis = 14695981039346656037ULL; - ca::u64 state = kOffsetBasis; - hashFingerprintLength(state, bytes.size()); - hashFingerprintBytes(state, bytes.data(), bytes.size()); - std::ostringstream output; - output << "fnv1a64:" << std::hex << std::setw(16) << std::setfill('0') << state; - return output.str(); -} - -WorkspaceResult fingerprintContainer(const model::ResourceContainer& container) -{ - constexpr ca::u64 kOffsetBasis = 14695981039346656037ULL; - ca::u64 state = kOffsetBasis; - std::vector uris; - container.visitResources( - [&](std::string_view uri, const model::IResource&) { uris.push_back(std::string(uri)); }); - std::sort(uris.begin(), uris.end()); - for (const auto& uri : uris) { - const model::IResource* resource = container.getResource(uri); - if (resource == nullptr) continue; - auto bytes = bytesOf(*resource); - if (bytes.is_err()) return ca::core::Err(std::move(bytes).unwrap_err()); - const auto& content = bytes.unwrap(); - hashFingerprintLength(state, uri.size()); - hashFingerprintBytes(state, reinterpret_cast(uri.data()), uri.size()); - hashFingerprintLength(state, content.size()); - hashFingerprintBytes(state, content.data(), content.size()); - } - std::ostringstream output; - output << "fnv1a64:" << std::hex << std::setw(16) << std::setfill('0') << state; - return ca::core::Ok(output.str()); -} - -// ── 导出目的地与原子导出 ─────────────────────────────────────── - -WorkspaceResult normalizedRegularFile(const std::string& path, - const std::string& operation) -{ - namespace fs = std::filesystem; - if (path.empty()) - return ca::core::Err( - error(WorkspaceError::InvalidArgument, operation, "path is empty")); - std::error_code filesystemError; - const auto absolute = fs::absolute(fsPath(path), filesystemError).lexically_normal(); - if (filesystemError) - return ca::core::Err( - error(WorkspaceError::InvalidArgument, operation, filesystemError.message())); - const auto status = fs::status(absolute, filesystemError); - if (filesystemError || !fs::exists(status)) - return ca::core::Err(error(WorkspaceError::NotFound, operation, pathToUtf8(absolute))); - if (!fs::is_regular_file(status)) - return ca::core::Err( - error(WorkspaceError::InvalidArgument, operation, "path is not a regular file")); - return ca::core::Ok(pathToUtf8(absolute)); -} - -WorkspaceResult validateExportDestination( - const std::string& sourcePath, const std::string& outputPath, - const std::string& operation = "export JAR", - const std::string& sourceDescription = "input JAR") -{ - namespace fs = std::filesystem; - if (outputPath.empty()) - return ca::core::Err( - error(WorkspaceError::InvalidArgument, operation, "output path is empty")); - - std::error_code filesystemError; - const auto output = fs::absolute(fsPath(outputPath), filesystemError); - if (filesystemError) - return ca::core::Err(error(WorkspaceError::InvalidArgument, operation, outputPath)); - const auto source = fs::absolute(fsPath(sourcePath), filesystemError); - if (!filesystemError && source == output) { - return ca::core::Err(error(WorkspaceError::PermissionDenied, operation, - "output must not overwrite the " + sourceDescription)); - } - const auto parent = output.parent_path(); - if (!parent.empty() && !fs::exists(parent)) { - return ca::core::Err( - error(WorkspaceError::NotFound, operation, "output parent does not exist")); - } - if (fs::exists(output)) - return ca::core::Err(error(WorkspaceError::AlreadyExists, operation, pathToUtf8(output))); - return ca::core::Ok(output); -} - -WorkspaceErrorInfo exportContainerToJar(model::ResourceContainer& container, - const std::string& sourcePath, - const std::string& outputPath, ca::u64 revision) -{ - namespace fs = std::filesystem; - auto output = validateExportDestination(sourcePath, outputPath); - if (output.is_err()) return std::move(output).unwrap_err(); - const auto destination = std::move(output).unwrap(); - - std::error_code filesystemError; - auto temporary = destination; - temporary += ".mjt-tmp-" + std::to_string(revision); - if (fs::exists(temporary)) - return error(WorkspaceError::AlreadyExists, "export JAR", pathToUtf8(temporary)); - try { - auto written = model::ResourceHelper::exportToJar(container, pathToUtf8(temporary)); - if (written.is_err()) { - fs::remove(temporary, filesystemError); - return modelFailure("export JAR", std::move(written).unwrap_err()); - } - if (!fs::exists(temporary) || fs::file_size(temporary) == 0) { - fs::remove(temporary, filesystemError); - return error(WorkspaceError::Internal, "export JAR", - "temporary archive was not written"); - } - fs::rename(temporary, destination, filesystemError); - if (filesystemError) { - fs::remove(temporary, filesystemError); - return error(WorkspaceError::Internal, "export JAR", "atomic rename failed"); - } - return WorkspaceErrorInfo{}; - } - catch (const std::exception& exception) { - fs::remove(temporary, filesystemError); - return error(WorkspaceError::Internal, "export JAR", exception.what()); - } -} - -WorkspaceErrorInfo exportContainerToClass(model::ResourceContainer& container, - const std::string& classUri, - const std::string& sourcePath, - const std::string& outputPath, ca::u64 revision) -{ - namespace fs = std::filesystem; - auto output = - validateExportDestination(sourcePath, outputPath, "export class", "input class"); - if (output.is_err()) return std::move(output).unwrap_err(); - const auto destination = std::move(output).unwrap(); - - std::error_code filesystemError; - auto temporary = destination; - temporary += ".mjbase-class-tmp-" + std::to_string(revision); - if (fs::exists(temporary)) - return error(WorkspaceError::AlreadyExists, "export class", pathToUtf8(temporary)); - try { - auto written = - model::ResourceHelper::exportToClassFile(container, pathToUtf8(temporary), classUri); - if (written.is_err()) { - fs::remove(temporary, filesystemError); - return modelFailure("export class", std::move(written).unwrap_err()); - } - if (!fs::exists(temporary) || fs::file_size(temporary) == 0) { - fs::remove(temporary, filesystemError); - return error(WorkspaceError::Internal, "export class", - "temporary classfile was not written"); - } - fs::rename(temporary, destination, filesystemError); - if (filesystemError) { - fs::remove(temporary, filesystemError); - return error(WorkspaceError::Internal, "export class", "atomic rename failed"); - } - return WorkspaceErrorInfo{}; - } - catch (const std::exception& exception) { - fs::remove(temporary, filesystemError); - return error(WorkspaceError::Internal, "export class", exception.what()); - } -} - -template -jasm::Result jasmFailure(const std::string& message) -{ - jasm::Result result; - result.diagnostics.push_back( - {jasm::DiagnosticSeverity::Error, jasm::DiagnosticStage::Resource, utf16(message), - std::nullopt}); - return result; -} - -void sortUnique(std::vector& values) -{ - std::sort(values.begin(), values.end()); - values.erase(std::unique(values.begin(), values.end()), values.end()); -} - -} // namespace - -// ── 错误码短名 ────────────────────────────────────────────────────────── - -const char* to_string(WorkspaceError errorCode) noexcept -{ - switch (errorCode) { - case WorkspaceError::Ok: return "Ok"; - case WorkspaceError::InvalidArgument: return "InvalidArgument"; - case WorkspaceError::NotFound: return "NotFound"; - case WorkspaceError::AlreadyExists: return "AlreadyExists"; - case WorkspaceError::FailedPrecondition: return "FailedPrecondition"; - case WorkspaceError::PermissionDenied: return "PermissionDenied"; - case WorkspaceError::Aborted: return "Aborted"; - case WorkspaceError::DataLoss: return "DataLoss"; - case WorkspaceError::Internal: return "Internal"; - case WorkspaceError::Unimplemented: return "Unimplemented"; - } - return "?"; -} - -// ── WorkspaceBase 内部结构 ────────────────────────────────────────────── - -struct WorkspaceBase::SupportingResource -{ - std::string id; - std::string path; - std::string fingerprint; - std::string role = "support"; ///< 角色标签 - std::unique_ptr container; -}; - -/// @brief 层次/覆盖分析图:KeyPool + ClassHierarchy + 每类声明摘要。 -/// -/// 未登记的超类型(java/lang/Object 等)物化为合成节点(无成员、access 0), -/// 使 superclass 链/接口闭包可统一遍历。 -struct WorkspaceBase::AnalysisGraph -{ - std::shared_ptr pool = std::make_shared(); - std::unique_ptr hierarchy = nullptr; - std::unordered_map summaries; ///< key: ClassKey.id - - AnalysisGraph() - : hierarchy(std::make_unique(*pool)) - {} - - bool declaresMethod(ca::u32 classKeyId, const std::string& name, - const std::string& descriptor) const - { - const auto found = summaries.find(classKeyId); - if (found == summaries.end()) return false; - for (const auto& method : found->second.methods) { - if (pool->str(method.name) == name && pool->str(method.desc) == descriptor) return true; - } - return false; - } -}; - -WorkspaceBase::WorkspaceBase() = default; -WorkspaceBase::~WorkspaceBase() = default; - -// ── 打开 / supporting ─────────────────────────────────────────────────── - -WorkspaceResult WorkspaceBase::openJar(const std::string& path) -{ - // 缺省空 decoder = 原始字节直通(#1110 族缺省零漂移,见 decoder 重载)。 - return openJar(path, {}); -} - -WorkspaceResult WorkspaceBase::openJar( - const std::string& path, const mjbase::jar::IJarReader::EntryNameDecoder& decoder) -{ - if (path.empty()) - return ca::core::Err(error(WorkspaceError::InvalidArgument, "open JAR", "path is empty")); - namespace fs = std::filesystem; - std::error_code filesystemError; - const auto status = fs::status(fsPath(path), filesystemError); - if (filesystemError || !fs::exists(status)) - return ca::core::Err(error(WorkspaceError::NotFound, "open JAR", path)); - if (!fs::is_regular_file(status)) - return ca::core::Err( - error(WorkspaceError::InvalidArgument, "open JAR", "path is not a regular file")); - - try { - model::ResourceContainer loaded; - auto imported = model::ResourceHelper::importFromJar(loaded, path, decoder); - if (imported.is_err()) - return ca::core::Err(modelFailure("open JAR", std::move(imported).unwrap_err())); - auto original = cloneContainer(loaded); - if (original.is_err()) return ca::core::Err(std::move(original).unwrap_err()); - auto working = cloneContainer(loaded); - if (working.is_err()) return ca::core::Err(std::move(working).unwrap_err()); - - original_ = std::move(original).unwrap(); - working_ = std::move(working).unwrap(); - artifactKind_ = ArtifactKind::Jar; - sourcePath_ = path; - primaryClassUri_.clear(); - supporting_.clear(); - // 会话史清零(旧语义:打开新主资源丢弃 undo/redo/savepoint)。绑定态 - // 先落盘旧容器台账再重置链(新容器台账随下次 flush 落盘;旧容器已落盘 - // 历史入待释放清单随之清理——台账作用域 = 容器)。 - auto digest = ledgerBaseDigest(); - if (digest.is_err()) return ca::core::Err(std::move(digest).unwrap_err()); - if (ledger_.bound()) { - if (ledger_.hasPending()) { - auto flushed = ledger_.flush(project::rfc3339UtcNow()); - if (flushed.is_err()) - return ca::core::Err(ledgerFailure("open JAR", std::move(flushed).unwrap_err())); - } - ledger_.resetSession(project::copyLedgerText(digest.unwrap())); - } - else { - auto fresh = project::EditLedgerStore::createInMemory(project::copyLedgerText(digest.unwrap())); - if (fresh.is_err()) - return ca::core::Err(ledgerFailure("open JAR", std::move(fresh).unwrap_err())); - ledger_ = std::move(fresh).unwrap(); - } - redoHint_.clear(); - revision_ = 0; - nextSupportingId_ = 1; - return ca::core::Ok(); - } - catch (const std::exception& exception) { - return ca::core::Err(error(WorkspaceError::DataLoss, "open JAR", exception.what())); - } -} - -WorkspaceResult WorkspaceBase::openClass(const std::string& path) -{ - if (path.empty()) - return ca::core::Err( - error(WorkspaceError::InvalidArgument, "open class", "path is empty")); - namespace fs = std::filesystem; - std::error_code filesystemError; - const auto status = fs::status(fsPath(path), filesystemError); - if (filesystemError || !fs::exists(status)) - return ca::core::Err(error(WorkspaceError::NotFound, "open class", path)); - if (!fs::is_regular_file(status)) - return ca::core::Err( - error(WorkspaceError::InvalidArgument, "open class", "path is not a regular file")); - - try { - model::ResourceContainer imported; - auto importedStatus = model::ResourceHelper::importFromClassFile(imported, path); - if (importedStatus.is_err()) - return ca::core::Err( - modelFailure("open class", std::move(importedStatus).unwrap_err())); - const auto* importedClass = - ca::core::tag_cast::dyn_cast(imported.getResource(path)); - if (importedClass == nullptr) { - return ca::core::Err( - error(WorkspaceError::DataLoss, "open class", "class resource was not imported")); - } - auto info = decodeClass(path, *importedClass); - if (info.is_err()) return ca::core::Err(std::move(info).unwrap_err()); - auto decoded = std::move(info).unwrap(); - if (decoded.internalName.empty()) { - return ca::core::Err(error(WorkspaceError::DataLoss, "open class", - "class has an empty internal name")); - } - - const auto uri = decoded.internalName + ".class"; - model::ResourceContainer loaded; - loaded.addResource(uri, - std::make_unique(uri, importedClass->get())); - std::vector diagnostics; - // openClass 门禁只覆盖本类(作用域收窄;relaxed 语义下协变捷径 - // 基线类照常打开)。 - const WorkspaceErrorInfo validation = - validateCandidate(loaded, diagnostics, jasm::ValidationMode::Relaxed, {uri}); - if (validation.is_err()) { - return ca::core::Err(error(WorkspaceError::DataLoss, "open class", - "class did not pass validation")); - } - - auto original = cloneContainer(loaded); - if (original.is_err()) return ca::core::Err(std::move(original).unwrap_err()); - auto working = cloneContainer(loaded); - if (working.is_err()) return ca::core::Err(std::move(working).unwrap_err()); - - original_ = std::move(original).unwrap(); - working_ = std::move(working).unwrap(); - artifactKind_ = ArtifactKind::Class; - sourcePath_ = path; - primaryClassUri_ = uri; - supporting_.clear(); - auto digest = ledgerBaseDigest(); - if (digest.is_err()) return ca::core::Err(std::move(digest).unwrap_err()); - if (ledger_.bound()) { - if (ledger_.hasPending()) { - auto flushed = ledger_.flush(project::rfc3339UtcNow()); - if (flushed.is_err()) - return ca::core::Err( - ledgerFailure("open class", std::move(flushed).unwrap_err())); - } - ledger_.resetSession(project::copyLedgerText(digest.unwrap())); - } - else { - auto fresh = project::EditLedgerStore::createInMemory(project::copyLedgerText(digest.unwrap())); - if (fresh.is_err()) - return ca::core::Err(ledgerFailure("open class", std::move(fresh).unwrap_err())); - ledger_ = std::move(fresh).unwrap(); - } - redoHint_.clear(); - revision_ = 0; - nextSupportingId_ = 1; - return ca::core::Ok(); - } - catch (const std::exception& exception) { - return ca::core::Err(error(WorkspaceError::DataLoss, "open class", exception.what())); - } -} - -WorkspaceResult WorkspaceBase::addSupportingJar(const std::string& path) -{ - return addSupportingRole(path, "support"); -} - -WorkspaceResult WorkspaceBase::addSupportingContainer( - const std::string& path, model::ContainerRole role) -{ - // primary 由 project_open 绑定,不接受挂载登记。 - if (role == model::ContainerRole::Primary) - return ca::core::Err(error(WorkspaceError::InvalidArgument, "add supporting container", - "primary role is bound by project open")); - return addSupportingRole(path, std::string{model::to_string(role)}); -} - -WorkspaceResult WorkspaceBase::addSupportingRole( - const std::string& path, const std::string& roleLabel) -{ - WorkspaceErrorInfo loaded = ensureLoaded("add supporting JAR"); - if (loaded.is_err()) return ca::core::Err(std::move(loaded)); - auto normalized = normalizedRegularFile(path, "add supporting JAR"); - if (normalized.is_err()) return ca::core::Err(std::move(normalized).unwrap_err()); - const auto normalPath = std::move(normalized).unwrap(); - for (const auto& support : supporting_) { - // 路径唯一约束跨角色生效:同一容器换角色挂载不追加第二条记录。 - if (support.path == normalPath) { - return ca::core::Err(error(WorkspaceError::AlreadyExists, "add supporting JAR", - "supporting path is already attached")); - } - } - try { - auto container = std::make_unique(); - auto imported = model::ResourceHelper::importFromJar(*container, normalPath); - if (imported.is_err()) - return ca::core::Err( - modelFailure("add supporting JAR", std::move(imported).unwrap_err())); - auto fingerprint = fingerprintContainer(*container); - if (fingerprint.is_err()) return ca::core::Err(std::move(fingerprint).unwrap_err()); - const auto fingerprintText = std::move(fingerprint).unwrap(); - for (const auto& support : supporting_) { - if (support.fingerprint == fingerprintText) { - return ca::core::Err(error(WorkspaceError::AlreadyExists, "add supporting JAR", - "supporting content is already attached")); - } - } - - SupportingResourceInfo info; - info.id = "support-" + std::to_string(nextSupportingId_++); - info.path = normalPath; - info.fingerprint = fingerprintText; - info.resourceCount = container->size(); - info.role = roleLabel; - container->visitResources([&](std::string_view, const model::IResource& resource) { - if (resource.getType() == model::ResourceType::Class) ++info.classCount; - }); - supporting_.push_back( - {info.id, info.path, info.fingerprint, roleLabel, std::move(container)}); - return ca::core::Ok(std::move(info)); - } - catch (const std::exception& exception) { - return ca::core::Err( - error(WorkspaceError::DataLoss, "add supporting JAR", exception.what())); - } -} - -WorkspaceResult WorkspaceBase::removeSupportingJar(const std::string& id) -{ - WorkspaceErrorInfo loaded = ensureLoaded("remove supporting JAR"); - if (loaded.is_err()) return ca::core::Err(std::move(loaded)); - if (id.empty()) { - return ca::core::Err(error(WorkspaceError::InvalidArgument, "remove supporting JAR", - "support ID is empty")); - } - const auto found = - std::find_if(supporting_.begin(), supporting_.end(), - [&](const SupportingResource& support) { return support.id == id; }); - if (found == supporting_.end()) - return ca::core::Err(error(WorkspaceError::NotFound, "remove supporting JAR", id)); - - // 一容器一记录:摘除即清理该容器的全部角色注册,无残留半边。 - SupportingResourceInfo info; - info.id = found->id; - info.path = found->path; - info.fingerprint = found->fingerprint; - info.resourceCount = found->container->size(); - info.role = found->role; - found->container->visitResources([&](std::string_view, const model::IResource& resource) { - if (resource.getType() == model::ResourceType::Class) ++info.classCount; - }); - supporting_.erase(found); - return ca::core::Ok(std::move(info)); -} - -// ── 状态面 ────────────────────────────────────────────────────────────── - -bool WorkspaceBase::isLoaded() const noexcept -{ - return working_ != nullptr && original_ != nullptr; -} - -ArtifactKind WorkspaceBase::artifactKind() const noexcept -{ - return artifactKind_; -} - -const std::string& WorkspaceBase::sourcePath() const noexcept -{ - return sourcePath_; -} - -const std::string& WorkspaceBase::primaryClassUri() const noexcept -{ - return primaryClassUri_; -} - -ca::u64 WorkspaceBase::revision() const noexcept -{ - return revision_; -} - -bool WorkspaceBase::isDirty() const -{ - if (!isLoaded()) return false; - return !diffContainers(*original_, *working_).empty(); -} - -bool WorkspaceBase::canExport() const noexcept -{ - return isLoaded(); -} - -std::vector WorkspaceBase::availablePasses() -{ - std::vector result; - for (const auto& pass : spi::defaultPassRegistry().list()) { - // scope 输出小写;mjbase 第四 scope Cf 无默认种子, - // 仅为穷尽 switch 兜底。 - const std::string scope = pass.scope == spi::PassScope::Method ? "method" - : pass.scope == spi::PassScope::Class ? "class" - : pass.scope == spi::PassScope::Global ? "global" - : "cf"; - result.push_back({std::string(pass.name), std::string(pass.description), scope}); - } - return result; -} - -std::vector WorkspaceBase::availablePipelines() -{ - spi::registerBuiltinPipelines(); - std::vector result; - for (const auto& pipeline : spi::PassRegistry::instance().listPipelines()) - result.push_back({pipeline.name, pipeline.description}); - return result; -} - -WorkspaceErrorInfo WorkspaceBase::ensureLoaded(const std::string& operation) const -{ - if (!isLoaded()) - return error(WorkspaceError::FailedPrecondition, operation, "workspace is not loaded"); - return WorkspaceErrorInfo{}; -} - -bool WorkspaceBase::accepts(const MutationRequest& request) const -{ - return request.expectedRevision == kAnyRevision || request.expectedRevision == revision_; -} - -MutationResult WorkspaceBase::revisionConflict() const -{ - MutationResult result; - result.status = error(WorkspaceError::Aborted, "mutate workspace", - "expected revision does not match current revision"); - result.revision = revision_; - result.exportable = canExport(); - return result; -} - -MutationResult WorkspaceBase::stateFailure(WorkspaceErrorInfo status) const -{ - MutationResult result; - result.status = std::move(status); - result.revision = revision_; - result.exportable = canExport(); - return result; -} - -model::ResourceContainer* WorkspaceBase::workingContainer() noexcept -{ - return working_.get(); -} - -const model::ResourceContainer* WorkspaceBase::workingContainer() const noexcept -{ - return working_.get(); -} - -std::unique_ptr WorkspaceBase::cloneWorking() const -{ - auto clone = cloneContainer(*working_); - if (clone.is_err()) throw std::runtime_error(std::move(clone).unwrap_err().message); - return std::move(clone).unwrap(); -} - -std::unique_ptr WorkspaceBase::cloneOriginal() const -{ - auto clone = cloneContainer(*original_); - if (clone.is_err()) throw std::runtime_error(std::move(clone).unwrap_err().message); - return std::move(clone).unwrap(); -} - -std::unique_ptr WorkspaceBase::analysisScanContainer() const -{ - auto merged = std::make_unique(); - working_->visitResources([&](std::string_view uri, const model::IResource& resource) { - auto copied = cloneResource(resource); - if (copied) merged->addResource(uri, std::move(copied)); - }); - for (const auto& support : supporting_) { - support.container->visitResources( - [&](std::string_view uri, const model::IResource& resource) { - if (merged->getResource(uri) != nullptr) return; // 主容器 URI 优先 - auto copied = cloneResource(resource); - if (copied) merged->addResource(uri, std::move(copied)); - }); - } - return merged; -} - -std::unordered_map WorkspaceBase::supportingClassIds() const -{ - std::unordered_set primary; - working_->visitResources([&](std::string_view, const model::IResource& resource) { - const auto* classResource = - ca::core::tag_cast::dyn_cast(&resource); - if (classResource == nullptr) return; - const auto name = internalNameOfBytes(classResource->get()); - if (!name.empty()) primary.insert(name); - }); - - std::unordered_map result; - for (const auto& support : supporting_) { - support.container->visitResources([&](std::string_view, const model::IResource& resource) { - const auto* classResource = - ca::core::tag_cast::dyn_cast(&resource); - if (classResource == nullptr) return; - const auto name = internalNameOfBytes(classResource->get()); - if (!name.empty() && primary.find(name) == primary.end()) - result.emplace(name, support.id); - }); - } - return result; -} - -void WorkspaceBase::annotateSupportingSources(std::vector& references) const -{ - const auto supportingClasses = supportingClassIds(); - for (auto& reference : references) { - const auto source = supportingClasses.find(reference.sourceClass); - if (source == supportingClasses.end()) continue; - reference.supporting = true; - reference.supportId = source->second; - } -} - -WorkspaceResult WorkspaceBase::analysisGraph() const -{ - WorkspaceErrorInfo status = ensureLoaded("build analysis classpath"); - if (status.is_err()) return ca::core::Err(std::move(status)); - try { - AnalysisGraph graph; - std::vector pending; - std::unordered_set registered; - - const auto appendClasses = [&](const model::ResourceContainer& source, bool primaryOwner) { - source.visitResources([&](std::string_view, const model::IResource& resource) { - const auto* classResource = - ca::core::tag_cast::dyn_cast(&resource); - if (classResource == nullptr) return; - auto session = sessionOf(classResource->get(), "build analysis classpath"); - if (session.is_err()) return; // 畸形支撑类不供层次事实(照旧容忍) - auto summary = hierarchy::fillSummary(*std::move(session).unwrap(), *graph.pool); - if (primaryOwner || registered.insert(summary.self.id).second) - pending.push_back(std::move(summary)); - }); - }; - appendClasses(*working_, true); - for (const auto& support : supporting_) appendClasses(*support.container, false); - - // 未登记超类型物化为合成节点(java/lang/Object 等;无成员、access 0)。 - std::unordered_set known; - for (const auto& summary : pending) known.insert(summary.self.id); - std::vector synthesized; - const auto ensureNode = [&](ca::u32 keyId) { - if (keyId == 0 || known.count(keyId) != 0) return; - known.insert(keyId); - hierarchy::ClassSummary stub; - stub.self = mjbase::ClassKey{keyId}; - synthesized.push_back(std::move(stub)); - }; - for (const auto& summary : pending) { - ensureNode(summary.super.id); - for (const auto& interfaceKey : summary.interfaces) ensureNode(interfaceKey.id); - } - - for (auto& summary : pending) { - if (graph.hierarchy->addClass(summary).is_ok()) - graph.summaries.emplace(summary.self.id, std::move(summary)); - } - for (auto& stub : synthesized) { - if (graph.hierarchy->addClass(stub).is_ok()) - graph.summaries.emplace(stub.self.id, std::move(stub)); - } - return ca::core::Ok(std::move(graph)); - } - catch (const std::exception& exception) { - return ca::core::Err( - error(WorkspaceError::Internal, "build analysis classpath", exception.what())); - } -} - -// ── 资源 / class 读取面 ───────────────────────────────────────────────── - -WorkspaceResult> WorkspaceBase::listResources( - bool includeClasses) const -{ - WorkspaceErrorInfo status = ensureLoaded("list resources"); - if (status.is_err()) return ca::core::Err(std::move(status)); - std::vector result; - working_->visitResources([&](std::string_view uri, const model::IResource& resource) { - if (!includeClasses && resource.getType() == model::ResourceType::Class) return; - auto bytes = bytesOf(resource); - if (bytes.is_err()) return; - result.push_back({std::string(uri), resourceTypeName(resource.getType()), - bytes.unwrap().size()}); - }); - std::sort(result.begin(), result.end(), - [](const ResourceSummary& left, const ResourceSummary& right) { - return left.uri < right.uri; - }); - return ca::core::Ok(std::move(result)); -} - -WorkspaceResult WorkspaceBase::readResource(const std::string& uri) const -{ - WorkspaceErrorInfo status = ensureLoaded("read resource"); - if (status.is_err()) return ca::core::Err(std::move(status)); - const model::IResource* resource = working_->getResource(uri); - if (resource == nullptr) - return ca::core::Err(error(WorkspaceError::NotFound, "read resource", uri)); - auto bytes = bytesOf(*resource); - if (bytes.is_err()) return ca::core::Err(std::move(bytes).unwrap_err()); - ResourceContent result; - result.summary = {uri, resourceTypeName(resource->getType()), bytes.unwrap().size()}; - result.bytes = std::move(bytes).unwrap(); - return ca::core::Ok(std::move(result)); -} - -WorkspaceResult WorkspaceBase::readClassBytes( - const std::string& uriOrInternalName) const -{ - WorkspaceErrorInfo status = ensureLoaded("read class bytes"); - if (status.is_err()) return ca::core::Err(std::move(status)); - std::string uri; - if (hasClassExtension(uriOrInternalName) && - working_->getResource(uriOrInternalName) != nullptr) { - uri = uriOrInternalName; - } - else { - auto resolved = resolveClassUri(*working_, uriOrInternalName); - if (resolved.is_err()) return ca::core::Err(std::move(resolved).unwrap_err()); - uri = std::move(resolved).unwrap(); - } - const model::IResource* resource = working_->getResource(uri); - if (resource == nullptr) - return ca::core::Err(error(WorkspaceError::NotFound, "read class bytes", uri)); - auto bytes = bytesOf(*resource); - if (bytes.is_err()) return ca::core::Err(std::move(bytes).unwrap_err()); - - ClassBytesInfo result; - result.resourceUri = uri; - result.bytes = std::move(bytes).unwrap(); - result.fingerprint = fingerprintBytes(result.bytes); - result.internalName = internalNameOfBytes(result.bytes); - return ca::core::Ok(std::move(result)); -} - -WorkspaceResult WorkspaceBase::resolveClassUri( - const model::ResourceContainer& container, const std::string& uriOrInternalName) const -{ - if (uriOrInternalName.empty()) - return ca::core::Err( - error(WorkspaceError::InvalidArgument, "resolve class", "selector is empty")); - if (ca::core::tag_cast::isa(container.getResource(uriOrInternalName))) - return ca::core::Ok(uriOrInternalName); - try { - // 内名 selector 命中 Multi-Release 族(base 与 versions/ 变体并存, - // 内名相同)时按确定序选定:base 形条目优先于版本化条目(JEP 238 口径 - // 版本 ≥ 9),同层级取 URI 字节序最小。不得依赖容器迭代序——那会让 - // 同一容器在不同进程/标准库里解析到不同族员。 - std::string match; - ca::i64 matchVersion = -1; - container.visitResources([&](std::string_view uri, const model::IResource& resource) { - const auto* classResource = - ca::core::tag_cast::dyn_cast(&resource); - if (classResource == nullptr) return; - if (internalNameOfBytes(classResource->get()) != uriOrInternalName) return; - const ca::i64 version = model::multiReleaseVersionOfClassUri(uri); - const bool candidateVersioned = version >= 9; - const bool currentVersioned = matchVersion >= 9; - const bool better = match.empty() || - (!candidateVersioned && currentVersioned) || - (candidateVersioned == currentVersioned && - std::string_view(uri) < std::string_view(match)); - if (!better) return; - match = std::string(uri); - matchVersion = version; - }); - if (match.empty()) - return ca::core::Err( - error(WorkspaceError::NotFound, "resolve class", uriOrInternalName)); - return ca::core::Ok(std::move(match)); - } - catch (const std::exception& exception) { - return ca::core::Err(error(WorkspaceError::DataLoss, "resolve class", exception.what())); - } -} - -WorkspaceResult> WorkspaceBase::listClassSummaries() const -{ - WorkspaceErrorInfo status = ensureLoaded("list class summaries"); - if (status.is_err()) return ca::core::Err(std::move(status)); - std::vector result; - working_->visitResources([&](std::string_view uri, const model::IResource& resource) { - const auto* classResource = - ca::core::tag_cast::dyn_cast(&resource); - if (classResource == nullptr) return; - auto decoded = decodeClassSummary(std::string(uri), *classResource); - if (decoded.is_err()) return; - result.push_back(std::move(decoded).unwrap()); - }); - std::sort(result.begin(), result.end(), - [](const ClassSummary& left, const ClassSummary& right) { - if (left.internalName != right.internalName) - return left.internalName < right.internalName; - return left.resourceUri < right.resourceUri; - }); - return ca::core::Ok(std::move(result)); -} - -WorkspaceResult> WorkspaceBase::listClasses() const -{ - WorkspaceErrorInfo status = ensureLoaded("list classes"); - if (status.is_err()) return ca::core::Err(std::move(status)); - std::vector result; - working_->visitResources([&](std::string_view uri, const model::IResource& resource) { - const auto* classResource = - ca::core::tag_cast::dyn_cast(&resource); - if (classResource == nullptr) return; - auto decoded = decodeClass(std::string(uri), *classResource); - if (decoded.is_err()) return; - result.push_back(std::move(decoded).unwrap()); - }); - std::sort(result.begin(), result.end(), [](const ClassInfo& left, const ClassInfo& right) { - if (left.internalName != right.internalName) return left.internalName < right.internalName; - return left.resourceUri < right.resourceUri; - }); - return ca::core::Ok(std::move(result)); -} - -WorkspaceResult WorkspaceBase::classInfo(const std::string& uriOrInternalName) const -{ - WorkspaceErrorInfo status = ensureLoaded("get class info"); - if (status.is_err()) return ca::core::Err(std::move(status)); - auto uri = resolveClassUri(*working_, uriOrInternalName); - if (uri.is_err()) return ca::core::Err(std::move(uri).unwrap_err()); - const auto* resource = ca::core::tag_cast::dyn_cast( - working_->getResource(uri.unwrap())); - if (resource == nullptr) - return ca::core::Err(error(WorkspaceError::NotFound, "get class info", uri.unwrap())); - return decodeClass(uri.unwrap(), *resource); -} - -WorkspaceResult WorkspaceBase::methodInfo(const std::string& owner, - const std::string& name, - const std::string& descriptor) const -{ - if (name.empty() || descriptor.empty()) { - return ca::core::Err(error(WorkspaceError::InvalidArgument, "get method info", - "name and descriptor are required")); - } - auto decoded = classInfo(owner); - if (decoded.is_err()) return ca::core::Err(std::move(decoded).unwrap_err()); - for (const auto& method : decoded.unwrap().methods) { - if (method.name == name && method.descriptor == descriptor) return ca::core::Ok(method); - } - return ca::core::Err( - error(WorkspaceError::NotFound, "get method info", owner + "." + name + descriptor)); -} - -WorkspaceResult WorkspaceBase::fieldInfo(const std::string& owner, - const std::string& name, - const std::string& descriptor) const -{ - WorkspaceErrorInfo status = ensureLoaded("get field info"); - if (status.is_err()) return ca::core::Err(std::move(status)); - if (owner.empty() || name.empty() || descriptor.empty()) { - return ca::core::Err(error(WorkspaceError::InvalidArgument, "get field info", - "owner, name and descriptor are required")); - } - auto uri = resolveClassUri(*working_, owner); - if (uri.is_err()) return ca::core::Err(std::move(uri).unwrap_err()); - const auto* resource = ca::core::tag_cast::dyn_cast( - working_->getResource(uri.unwrap())); - if (resource == nullptr) - return ca::core::Err(error(WorkspaceError::NotFound, "get field info", owner)); - auto session = sessionOf(resource->get(), "get field info"); - if (session.is_err()) return ca::core::Err(std::move(session).unwrap_err()); - auto held = std::move(session).unwrap(); - jbc::core::ClassFile& cf = held->backing(); - for (auto& field : cf.fields) { - if (utf8Text(cf, field.nameCp) == name && utf8Text(cf, field.descCp) == descriptor) - return ca::core::Ok(makeFieldInfo(cf, field)); - } - return ca::core::Err( - error(WorkspaceError::NotFound, "get field info", owner + "." + name + descriptor)); -} - -WorkspaceResult> WorkspaceBase::constantPool( - const std::string& uriOrInternalName, bool resolve) const -{ - WorkspaceErrorInfo status = ensureLoaded("get constant pool"); - if (status.is_err()) return ca::core::Err(std::move(status)); - auto uri = resolveClassUri(*working_, uriOrInternalName); - if (uri.is_err()) return ca::core::Err(std::move(uri).unwrap_err()); - const auto* resource = ca::core::tag_cast::dyn_cast( - working_->getResource(uri.unwrap())); - if (resource == nullptr) - return ca::core::Err(error(WorkspaceError::NotFound, "get constant pool", uri.unwrap())); - auto session = sessionOf(resource->get(), "get constant pool"); - if (session.is_err()) return ca::core::Err(std::move(session).unwrap_err()); - auto held = std::move(session).unwrap(); - const jbc::core::ClassFile& cf = held->backing(); - std::vector result; - for (ca::u32 index = 1; index < cf.cp.size(); ++index) { - result.push_back(support::constantPoolEntry(cf, index, resolve)); - } - return ca::core::Ok(std::move(result)); -} - -WorkspaceResult WorkspaceBase::methodInstructions( - const std::string& owner, const std::string& name, const std::string& descriptor, - bool resolve) const -{ - WorkspaceErrorInfo status = ensureLoaded("get method instructions"); - if (status.is_err()) return ca::core::Err(std::move(status)); - if (owner.empty() || name.empty() || descriptor.empty()) { - return ca::core::Err(error(WorkspaceError::InvalidArgument, "get method instructions", - "owner, name and descriptor are required")); - } - auto uri = resolveClassUri(*working_, owner); - if (uri.is_err()) return ca::core::Err(std::move(uri).unwrap_err()); - const auto* resource = ca::core::tag_cast::dyn_cast( - working_->getResource(uri.unwrap())); - if (resource == nullptr) - return ca::core::Err(error(WorkspaceError::NotFound, "get method instructions", owner)); - auto session = sessionOf(resource->get(), "get method instructions"); - if (session.is_err()) return ca::core::Err(std::move(session).unwrap_err()); - auto held = std::move(session).unwrap(); - jbc::core::ClassFile& cf = held->backing(); - for (ca::usize methodIndex = 0; methodIndex < cf.methods.size(); ++methodIndex) { - auto& method = cf.methods[methodIndex]; - if (utf8Text(cf, method.nameCp) != name || utf8Text(cf, method.descCp) != descriptor) - continue; - jbc::core::CodeAttr* code = support::ensureCode(cf, method); - if (code == nullptr) - return ca::core::Err(error(WorkspaceError::NotFound, "get method instructions", - "method has no Code attribute")); - auto promoted = jbc::ir::promoteMethod(*held, methodIndex); - if (promoted.is_err()) - return ca::core::Err(error(WorkspaceError::DataLoss, "get method instructions", - "method body cannot be promoted")); - auto raw = promoted.unwrap()->dom.exportRaw(); - if (raw.is_err()) - return ca::core::Err(error(WorkspaceError::DataLoss, "get method instructions", - "method body cannot be exported")); - const auto insns = std::move(raw).unwrap(); - MethodInstructions result; - result.maxStack = static_cast(code->maxStack); - result.maxLocals = static_cast(code->maxLocals); - for (const auto& insn : insns) { - InstructionDetailInfo rendered = renderInstruction(cf, insn); - if (resolve) rendered.operandComment = ldcComment(cf, insn.cp); - result.instructions.push_back(std::move(rendered)); - } - return ca::core::Ok(std::move(result)); - } - return ca::core::Err(error(WorkspaceError::NotFound, "get method instructions", - owner + "." + name + descriptor)); -} - -WorkspaceResult WorkspaceBase::methodBytecode(const std::string& owner, - const std::string& name, - const std::string& descriptor) const -{ - WorkspaceErrorInfo status = ensureLoaded("get method bytecode"); - if (status.is_err()) return ca::core::Err(std::move(status)); - if (owner.empty() || name.empty() || descriptor.empty()) { - return ca::core::Err(error(WorkspaceError::InvalidArgument, "get method bytecode", - "owner, name and descriptor are required")); - } - auto uri = resolveClassUri(*working_, owner); - if (uri.is_err()) return ca::core::Err(std::move(uri).unwrap_err()); - const auto* resource = ca::core::tag_cast::dyn_cast( - working_->getResource(uri.unwrap())); - if (resource == nullptr) - return ca::core::Err(error(WorkspaceError::NotFound, "get method bytecode", owner)); - auto session = sessionOf(resource->get(), "get method bytecode"); - if (session.is_err()) return ca::core::Err(std::move(session).unwrap_err()); - auto held = std::move(session).unwrap(); - jbc::core::ClassFile& cf = held->backing(); - for (ca::usize methodIndex = 0; methodIndex < cf.methods.size(); ++methodIndex) { - auto& method = cf.methods[methodIndex]; - if (utf8Text(cf, method.nameCp) != name || utf8Text(cf, method.descCp) != descriptor) - continue; - auto info = makeMethodInfo(*held, method, methodIndex); - if (info.is_err()) return ca::core::Err(std::move(info).unwrap_err()); - MethodBytecode result; - result.method = std::move(info).unwrap(); - if (support::ensureCode(cf, method) != nullptr) { - auto promoted = jbc::ir::promoteMethod(*held, methodIndex); - if (promoted.is_err()) - return ca::core::Err(error(WorkspaceError::DataLoss, "get method bytecode", - "method body cannot be promoted")); - auto raw = promoted.unwrap()->dom.exportRaw(); - if (raw.is_err()) - return ca::core::Err(error(WorkspaceError::DataLoss, "get method bytecode", - "method body cannot be exported")); - const auto insns = std::move(raw).unwrap(); - ca::i32 index = 0; - for (const auto& insn : insns) { - InstructionInfo item; - item.index = index++; - item.opcode = static_cast(insn.op); - const char* mnemonic = jbc::core::mnemonicOf(insn.op); - item.mnemonic = mnemonic != nullptr ? mnemonic : "?"; - item.nodeType = instructionNodeType(insn.op); - result.instructions.push_back(std::move(item)); - } - } - return ca::core::Ok(std::move(result)); - } - return ca::core::Err(error(WorkspaceError::NotFound, "get method bytecode", - owner + "." + name + descriptor)); -} - -jasm::Result WorkspaceBase::getMethodJasm( - const std::string& owner, const std::string& name, const std::string& descriptor) const -{ - if (!isLoaded()) return jasmFailure("workspace is not loaded"); - auto uri = resolveClassUri(*working_, owner); - if (uri.is_err()) { - return jasmFailure(std::move(uri).unwrap_err().message); - } - const auto* resource = ca::core::tag_cast::dyn_cast( - working_->getResource(uri.unwrap())); - if (resource == nullptr) - return jasmFailure("Class resource not found: " + owner); - const auto nameRef = ca::str::Utf8StringRef::from_data( - reinterpret_cast(name.data()), name.size()); - const auto descriptorRef = ca::str::Utf8StringRef::from_data( - reinterpret_cast(descriptor.data()), descriptor.size()); - return jasm::JvmJasmService{}.disassembleMethod(resource->get(), nameRef, descriptorRef); -} - -WorkspaceResult> WorkspaceBase::manifestAttributes() const -{ - WorkspaceErrorInfo status = ensureLoaded("read manifest"); - if (status.is_err()) return ca::core::Err(std::move(status)); - const model::ManifestResource* manifest = nullptr; - working_->visitResources([&](std::string_view, const model::IResource& resource) { - if (manifest == nullptr) - manifest = ca::core::tag_cast::dyn_cast(&resource); - }); - if (manifest == nullptr) - return ca::core::Err(error(WorkspaceError::NotFound, "read manifest", "MANIFEST.MF")); - std::vector result; - result.reserve(manifest->get().getMainAttributes().size()); - for (const auto& [key, value] : manifest->get().getMainAttributes()) { - result.push_back({key.to_std_string(), value.to_std_string()}); - } - std::sort(result.begin(), result.end(), - [](const ManifestAttribute& left, const ManifestAttribute& right) { - return left.key < right.key; - }); - return ca::core::Ok(std::move(result)); -} - -WorkspaceResult> WorkspaceBase::resourceStrings( - ca::usize minimumLength) const -{ - WorkspaceErrorInfo status = ensureLoaded("extract resource strings"); - if (status.is_err()) return ca::core::Err(std::move(status)); - if (minimumLength == 0) { - return ca::core::Err(error(WorkspaceError::InvalidArgument, "extract resource strings", - "minimum length must be positive")); - } - try { - std::vector result; - working_->visitResources([&](std::string_view uriView, const model::IResource& resource) { - if (resource.getType() == model::ResourceType::Class) return; - auto bytes = bytesOf(resource); - if (bytes.is_err()) return; - const auto& value = bytes.unwrap(); - const std::string uri{uriView}; - ca::usize start = 0; - for (ca::usize index = 0; index <= value.size(); ++index) { - const bool printable = - index < value.size() && value[index] >= 0x20 && value[index] <= 0x7e; - if (printable) continue; - if (index - start >= minimumLength) { - result.push_back( - {uri, start, - std::string(reinterpret_cast(value.data() + start), - index - start)}); - } - start = index + 1; - } - }); - std::sort(result.begin(), result.end(), - [](const ResourceStringInfo& left, const ResourceStringInfo& right) { - if (left.uri != right.uri) return left.uri < right.uri; - if (left.offset != right.offset) return left.offset < right.offset; - return left.value < right.value; - }); - return ca::core::Ok(std::move(result)); - } - catch (const std::exception& exception) { - return ca::core::Err( - error(WorkspaceError::Internal, "extract resource strings", exception.what())); - } -} - -// ── 搜索面 ────────────────────────────────────────────────────────────── - -WorkspaceResult> WorkspaceBase::searchClasses( - const std::string& query, const SearchOptions& options) const -{ - WorkspaceErrorInfo status = ensureLoaded("search classes"); - if (status.is_err()) return ca::core::Err(std::move(status)); - auto compiled = compileSearchRegex(query, options, "search classes"); - if (compiled.is_err()) return ca::core::Err(std::move(compiled).unwrap_err()); - auto compiledRegex = std::move(compiled).unwrap(); - const auto* regex = compiledRegex ? &*compiledRegex : nullptr; - - std::vector result; - working_->visitResources([&](std::string_view uriView, const model::IResource& resource) { - const auto* classResource = - ca::core::tag_cast::dyn_cast(&resource); - if (classResource == nullptr) return; - const std::string uri{uriView}; - auto decoded = decodeClass(uri, *classResource); - if (decoded.is_err()) return; - const auto& info = decoded.unwrap(); - if (!matchesSearchScope(info.internalName, options)) return; - if (!matchesAnyText({info.internalName, info.resourceUri}, query, options, regex)) return; - result.push_back( - {info.resourceUri, "class", info.internalName, {}, {}, -1, info.internalName}); - }); - sortAndDeduplicateSearchHits(result); - return ca::core::Ok(std::move(result)); -} - -WorkspaceResult> WorkspaceBase::searchMembers( - const std::string& query, const SearchOptions& options) const -{ - WorkspaceErrorInfo status = ensureLoaded("search members"); - if (status.is_err()) return ca::core::Err(std::move(status)); - auto compiled = compileSearchRegex(query, options, "search members"); - if (compiled.is_err()) return ca::core::Err(std::move(compiled).unwrap_err()); - auto compiledRegex = std::move(compiled).unwrap(); - const auto* regex = compiledRegex ? &*compiledRegex : nullptr; - - std::vector result; - working_->visitResources([&](std::string_view uriView, const model::IResource& resource) { - const auto* classResource = - ca::core::tag_cast::dyn_cast(&resource); - if (classResource == nullptr) return; - const std::string uri{uriView}; - auto decoded = decodeClass(uri, *classResource); - if (decoded.is_err()) return; - const auto& info = decoded.unwrap(); - if (!matchesSearchScope(info.internalName, options)) return; - for (const auto& field : info.fields) { - if (!options.memberKind.empty() && options.memberKind != "field") continue; - const auto searchable = info.internalName + " " + field.name + " " + - field.descriptor + " " + field.signature; - if (!matchesAnyText( - {info.internalName, field.name, field.descriptor, field.signature, searchable}, - query, options, regex)) { - continue; - } - result.push_back({info.resourceUri, "field", info.internalName, field.name, - field.descriptor, -1, field.name + field.descriptor}); - } - for (const auto& method : info.methods) { - if (!options.memberKind.empty() && options.memberKind != "method") continue; - const auto searchable = info.internalName + " " + method.name + " " + - method.descriptor + " " + method.signature; - if (!matchesAnyText({info.internalName, method.name, method.descriptor, - method.signature, searchable}, - query, options, regex)) { - continue; - } - result.push_back({info.resourceUri, "method", info.internalName, method.name, - method.descriptor, -1, method.name + method.descriptor}); - } - }); - sortAndDeduplicateSearchHits(result); - return ca::core::Ok(std::move(result)); -} - -WorkspaceResult> WorkspaceBase::searchStrings( - const std::string& query, const SearchOptions& options) const -{ - WorkspaceErrorInfo status = ensureLoaded("search strings"); - if (status.is_err()) return ca::core::Err(std::move(status)); - auto compiled = compileSearchRegex(query, options, "search strings"); - if (compiled.is_err()) return ca::core::Err(std::move(compiled).unwrap_err()); - auto compiledRegex = std::move(compiled).unwrap(); - const auto* regex = compiledRegex ? &*compiledRegex : nullptr; - - std::vector result; - - // 非 class 资源可打印串(无 owner 语义:owner/packagePrefix 过滤时不参与)。 - if (options.owner.empty() && options.packagePrefix.empty()) { - auto resources = resourceStrings(1); - if (resources.is_err()) return ca::core::Err(std::move(resources).unwrap_err()); - for (const auto& item : resources.unwrap()) { - if (!matchesText(item.value, query, options, regex)) continue; - result.push_back({item.uri, "resource_string", {}, {}, {}, -1, item.value}); - } - } - - working_->visitResources([&](std::string_view uriView, const model::IResource& resource) { - const auto* classResource = - ca::core::tag_cast::dyn_cast(&resource); - if (classResource == nullptr) return; - const std::string uri{uriView}; - auto session = sessionOf(classResource->get(), "search strings"); - if (session.is_err()) return; // 搜索对畸形 class 宽容(照旧) - auto held = std::move(session).unwrap(); - const jbc::core::ClassFile& cf = held->backing(); - const auto owner = mjbase::jasm::classFileUtf8ToString(cf.thisName()); - if (!matchesSearchScope(owner, options)) return; - //String 背书 utf8 去重:被 CONSTANT_String 引用的 utf8 条目由 - //constant_pool_string 行承载(string_literal 分面),不再以裸 utf8 行 - //重复出现并冒充类引用(评估反馈实证:字面量背书被标成 utf8_classref, - //string_kind 过滤失去意义)。 - std::set stringBackedUtf8; - for (ca::u32 index = 1; index < cf.cp.size(); ++index) { - if (!cf.cp.valid(index)) continue; - const auto& candidate = cf.cp.at(index); - if (candidate.tag == jbc::core::CpTag::String && cf.cp.valid(candidate.a)) - stringBackedUtf8.insert(candidate.a); - } - for (ca::u32 index = 1; index < cf.cp.size(); ++index) { - if (!cf.cp.valid(index)) continue; - const auto& entry = cf.cp.at(index); - std::string value; - std::string kind; - if (entry.tag == jbc::core::CpTag::Utf8) { - if (stringBackedUtf8.count(index) > 0) continue; - value = utf8Text(cf, index); - kind = "constant_pool_utf8"; - } - else if (entry.tag == jbc::core::CpTag::String) { - value = utf8Text(cf, entry.a); - kind = "constant_pool_string"; - } - else { - continue; - } - if (!matchesText(value, query, options, regex)) continue; - result.push_back({uri, kind, owner, {}, {}, -1, std::move(value)}); - } - }); - sortAndDeduplicateSearchHits(result); - return ca::core::Ok(std::move(result)); -} - -WorkspaceResult> WorkspaceBase::searchNumbers( - const std::string& query, const SearchOptions& options) const -{ - WorkspaceErrorInfo status = ensureLoaded("search numbers"); - if (status.is_err()) return ca::core::Err(std::move(status)); - status = validateSearchOptions(query, options, "search numbers"); - if (status.is_err()) return ca::core::Err(std::move(status)); - status = validateNumberQuery(query); - if (status.is_err()) return ca::core::Err(std::move(status)); - - std::vector result; - working_->visitResources([&](std::string_view uriView, const model::IResource& resource) { - const auto* classResource = - ca::core::tag_cast::dyn_cast(&resource); - if (classResource == nullptr) return; - const std::string uri{uriView}; - auto session = sessionOf(classResource->get(), "search numbers"); - if (session.is_err()) return; - auto held = std::move(session).unwrap(); - jbc::core::ClassFile& cf = held->backing(); - const auto owner = mjbase::jasm::classFileUtf8ToString(cf.thisName()); - if (!matchesSearchScope(owner, options)) return; - - if (options.opcode < 0) { - for (ca::u32 index = 1; index < cf.cp.size(); ++index) { - if (!cf.cp.valid(index)) continue; - const auto& entry = cf.cp.at(index); - std::string value; - switch (entry.tag) { - case jbc::core::CpTag::Integer: value = numberText(entry.ival); break; - case jbc::core::CpTag::Float: - value = numberText(support::bitsToFloat(static_cast(entry.ival))); - break; - case jbc::core::CpTag::Long: value = numberText(entry.lval); break; - case jbc::core::CpTag::Double: - value = numberText(support::bitsToDouble(static_cast(entry.lval))); - break; - default: continue; - } - if (!matchesNumber(value, query)) continue; - result.push_back( - {uri, "constant_pool_number", owner, {}, {}, -1, std::move(value)}); - } - } - - // 指令数值字面量:iconst/lconst/fconst/dconst 族 + 立即数/iinc/switch/ldc。 - for (auto& visit : jbc::ir::collectCodeUnits(*held)) { - if (visit.kind != jbc::ir::CodeVisit::Kind::Ok) continue; - auto raw = visit.unit->dom.exportRaw(); - if (raw.is_err()) continue; - const auto& insns = std::move(raw).unwrap(); - auto& method = cf.methods[visit.methodIdx]; - const std::string methodName = utf8Text(cf, method.nameCp); - const std::string methodDesc = utf8Text(cf, method.descCp); - for (ca::i32 index = 0; index < static_cast(insns.size()); ++index) { - const auto& insn = insns[static_cast(index)]; - if (options.opcode >= 0 && static_cast(insn.op) != options.opcode) - continue; - std::vector values; - switch (insn.op) { - case jbc::core::Op::ICONST_M1: values.push_back("-1"); break; - case jbc::core::Op::ICONST_0: - case jbc::core::Op::LCONST_0: - case jbc::core::Op::FCONST_0: - case jbc::core::Op::DCONST_0: values.push_back("0"); break; - case jbc::core::Op::ICONST_1: - case jbc::core::Op::LCONST_1: - case jbc::core::Op::FCONST_1: - case jbc::core::Op::DCONST_1: values.push_back("1"); break; - case jbc::core::Op::ICONST_2: - case jbc::core::Op::FCONST_2: values.push_back("2"); break; - case jbc::core::Op::ICONST_3: values.push_back("3"); break; - case jbc::core::Op::ICONST_4: values.push_back("4"); break; - case jbc::core::Op::ICONST_5: values.push_back("5"); break; - default: break; - } - switch (jbc::core::shapeOf(insn.op)) { - case jbc::core::Shape::Imm1: - case jbc::core::Shape::Imm2: - case jbc::core::Shape::Atype1: - case jbc::core::Shape::Local1: - values.push_back(numberText(insn.a)); - break; - case jbc::core::Shape::Iinc1: values.push_back(numberText(insn.b)); break; - case jbc::core::Shape::TableSwitch: - values.push_back(numberText(insn.a)); - values.push_back(numberText(insn.b)); - break; - case jbc::core::Shape::LookupSwitch: - for (const auto key : insn.cases) values.push_back(numberText(key.key)); - break; - case jbc::core::Shape::CP2Dims: values.push_back(numberText(insn.a)); break; - case jbc::core::Shape::CP1: - case jbc::core::Shape::CP2: - { - if (cf.cp.valid(insn.cp)) { - const auto& entry = cf.cp.at(insn.cp); - if (entry.tag == jbc::core::CpTag::Integer) - values.push_back(numberText(entry.ival)); - else if (entry.tag == jbc::core::CpTag::Long) - values.push_back(numberText(entry.lval)); - else if (entry.tag == jbc::core::CpTag::Float) - values.push_back(numberText( - support::bitsToFloat(static_cast(entry.ival)))); - else if (entry.tag == jbc::core::CpTag::Double) - values.push_back(numberText( - support::bitsToDouble(static_cast(entry.lval)))); - } - break; - } - default: break; - } - for (const auto& value : values) { - if (!matchesNumber(value, query)) continue; - result.push_back({uri, "instruction_number", owner, methodName, methodDesc, - index, value}); - } - } - } - }); - sortAndDeduplicateSearchHits(result); - return ca::core::Ok(std::move(result)); -} - -WorkspaceResult> WorkspaceBase::searchInstructions( - const std::string& query, const SearchOptions& options) const -{ - WorkspaceErrorInfo status = ensureLoaded("search instructions"); - if (status.is_err()) return ca::core::Err(std::move(status)); - auto compiled = compileSearchRegex(query, options, "search instructions"); - if (compiled.is_err()) return ca::core::Err(std::move(compiled).unwrap_err()); - auto compiledRegex = std::move(compiled).unwrap(); - const auto* regex = compiledRegex ? &*compiledRegex : nullptr; - - std::vector result; - working_->visitResources([&](std::string_view uriView, const model::IResource& resource) { - const auto* classResource = - ca::core::tag_cast::dyn_cast(&resource); - if (classResource == nullptr) return; - const std::string uri{uriView}; - auto session = sessionOf(classResource->get(), "search instructions"); - if (session.is_err()) return; - auto held = std::move(session).unwrap(); - jbc::core::ClassFile& cf = held->backing(); - const auto owner = mjbase::jasm::classFileUtf8ToString(cf.thisName()); - if (!matchesSearchScope(owner, options)) return; - for (auto& visit : jbc::ir::collectCodeUnits(*held)) { - if (visit.kind != jbc::ir::CodeVisit::Kind::Ok) continue; - auto raw = visit.unit->dom.exportRaw(); - if (raw.is_err()) continue; - const auto& insns = std::move(raw).unwrap(); - auto& method = cf.methods[visit.methodIdx]; - const std::string methodName = utf8Text(cf, method.nameCp); - const std::string methodDesc = utf8Text(cf, method.descCp); - for (ca::i32 index = 0; index < static_cast(insns.size()); ++index) { - const auto& insn = insns[static_cast(index)]; - if (options.opcode >= 0 && static_cast(insn.op) != options.opcode) - continue; - std::ostringstream output; - const char* mnemonic = jbc::core::mnemonicOf(insn.op); - output << (mnemonic != nullptr ? mnemonic : "?") << ' ' - << instructionNodeType(insn.op) << ' ' << static_cast(insn.op); - switch (jbc::core::shapeOf(insn.op)) { - case jbc::core::Shape::Imm1: - case jbc::core::Shape::Imm2: - case jbc::core::Shape::Atype1: - case jbc::core::Shape::Local1: - output << ' ' << insn.a; - break; - case jbc::core::Shape::Iinc1: output << ' ' << insn.a << ' ' << insn.b; break; - case jbc::core::Shape::CP2Dims: - output << ' ' << support::classNameOf(cf, insn.cp) << ' ' << insn.a; - break; - case jbc::core::Shape::CP2Count: - case jbc::core::Shape::CP2Zero2: - output << ' ' << memberRefFlatText(cf, insn.cp); - break; - case jbc::core::Shape::CP1: - case jbc::core::Shape::CP2: - { - if (cf.cp.valid(insn.cp)) { - const auto& entry = cf.cp.at(insn.cp); - if (entry.tag == jbc::core::CpTag::String) - output << ' ' << utf8Text(cf, entry.a); - else if (entry.tag == jbc::core::CpTag::Class) - output << ' ' << support::classNameOf(cf, insn.cp); - else if (entry.tag == jbc::core::CpTag::Integer) - output << ' ' << entry.ival; - else if (entry.tag == jbc::core::CpTag::Long) - output << ' ' << entry.lval; - else if (entry.tag == jbc::core::CpTag::Float) - output << ' ' - << support::bitsToFloat(static_cast(entry.ival)); - else if (entry.tag == jbc::core::CpTag::Double) - output << ' ' - << support::bitsToDouble(static_cast(entry.lval)); - else - { - // CP2 形态含 invoke*virtual/static/special 与字段/类访问 - // 指令。方法引用描述符以 '(' 开头自带分界,保持无冒号 - // 扁平口径(搜索文本契约);字段引用的名字与描述符 - // 连排不可读(评估反馈实证:outLjava/...),冒号分界。 - if (entry.tag == jbc::core::CpTag::Fieldref) - { - const auto reference = memberRefColonText(cf, insn.cp); - if (!reference.empty()) output << ' ' << reference; - } - else - { - const auto reference = memberRefFlatText(cf, insn.cp); - if (!reference.empty()) output << ' ' << reference; - } - } - } - break; - } - default: break; - } - const auto value = output.str(); - if (!matchesText(value, query, options, regex)) continue; - result.push_back({uri, "instruction", owner, methodName, methodDesc, index, value}); - } - } - }); - sortAndDeduplicateSearchHits(result); - return ca::core::Ok(std::move(result)); -} - -// ── 引用 / 层次分析面 ─────────────────────────────────────────────────── - -WorkspaceResult> WorkspaceBase::xrefsToMethod( - const std::string& owner, const std::string& name, const std::string& descriptor) const -{ - WorkspaceErrorInfo status = ensureLoaded("xref method"); - if (status.is_err()) return ca::core::Err(std::move(status)); - if (owner.empty() || name.empty() || descriptor.empty()) { - return ca::core::Err(error(WorkspaceError::InvalidArgument, "xref method", - "owner, name and descriptor are required")); - } - try { - ReferenceScan scan; - scan.build(*analysisScanContainer()); - std::vector result; - for (const auto& reference : scan.methodReferencesTo(owner, name, descriptor)) { - result.push_back(referenceInfo( - reference.site, - reference.target.owner + "." + reference.target.name + - reference.target.descriptor, - "direct")); - } - annotateSupportingSources(result); - sortReferences(result); - return ca::core::Ok(std::move(result)); - } - catch (const std::exception& exception) { - return ca::core::Err(error(WorkspaceError::Internal, "xref method", exception.what())); - } -} - -WorkspaceResult> WorkspaceBase::xrefsToField( - const std::string& owner, const std::string& name, const std::string& descriptor) const -{ - WorkspaceErrorInfo status = ensureLoaded("xref field"); - if (status.is_err()) return ca::core::Err(std::move(status)); - if (owner.empty() || name.empty() || descriptor.empty()) { - return ca::core::Err(error(WorkspaceError::InvalidArgument, "xref field", - "owner, name and descriptor are required")); - } - try { - ReferenceScan scan; - scan.build(*analysisScanContainer()); - std::vector result; - for (const auto& reference : scan.fieldReferencesTo(owner, name, descriptor)) { - result.push_back(referenceInfo( - reference.site, - reference.target.owner + "." + reference.target.name + - reference.target.descriptor, - "direct")); - } - annotateSupportingSources(result); - sortReferences(result); - return ca::core::Ok(std::move(result)); - } - catch (const std::exception& exception) { - return ca::core::Err(error(WorkspaceError::Internal, "xref field", exception.what())); - } -} - -WorkspaceResult> WorkspaceBase::xrefsToClass( - const std::string& internalName) const -{ - WorkspaceErrorInfo status = ensureLoaded("xref class"); - if (status.is_err()) return ca::core::Err(std::move(status)); - if (internalName.empty()) - return ca::core::Err( - error(WorkspaceError::InvalidArgument, "xref class", "class is empty")); - try { - ReferenceScan scan; - scan.build(*analysisScanContainer()); - std::vector result; - for (const auto& reference : scan.typeReferencesTo(internalName)) { - result.push_back(referenceInfo(reference.site, reference.targetType, "type")); - } - annotateSupportingSources(result); - sortReferences(result); - return ca::core::Ok(std::move(result)); - } - catch (const std::exception& exception) { - return ca::core::Err(error(WorkspaceError::Internal, "xref class", exception.what())); - } -} - -WorkspaceResult> WorkspaceBase::calleesOfMethod( - const std::string& owner, const std::string& name, const std::string& descriptor, - bool resolved) const -{ - WorkspaceErrorInfo status = ensureLoaded("list callees"); - if (status.is_err()) return ca::core::Err(std::move(status)); - if (owner.empty() || name.empty() || descriptor.empty()) { - return ca::core::Err(error(WorkspaceError::InvalidArgument, "list callees", - "owner, name and descriptor are required")); - } - try { - std::vector result; - if (!resolved) { - ReferenceScan scan; - scan.build(*analysisScanContainer()); - for (const auto& reference : scan.methodReferencesFrom(owner, name, descriptor)) { - result.push_back(referenceInfo( - reference.site, - reference.target.owner + "." + reference.target.name + - reference.target.descriptor, - "direct")); - } - } - else { - // 解析展开(mjbase 面):虚调用/接口调用沿层次图展开到「静态目标 + - // 后代中声明同名同描述符方法」的候选集合(虚分派可达面)。本近似 - // 覆盖 direct 与单继承/接口分派的展开语义——完整闭包由主线 - // ResolvedCallGraph 设施接管。 - auto graph = analysisGraph(); - if (graph.is_err()) return ca::core::Err(std::move(graph).unwrap_err()); - auto analysis = std::move(graph).unwrap(); - auto uri = resolveClassUri(*working_, owner); - if (uri.is_err()) return ca::core::Err(std::move(uri).unwrap_err()); - const auto* resource = ca::core::tag_cast::dyn_cast( - working_->getResource(uri.unwrap())); - if (resource == nullptr) - return ca::core::Err(error(WorkspaceError::NotFound, "list callees", owner)); - auto session = sessionOf(resource->get(), "list callees"); - if (session.is_err()) return ca::core::Err(std::move(session).unwrap_err()); - auto held = std::move(session).unwrap(); - jbc::core::ClassFile& cf = held->backing(); - for (ca::usize methodIndex = 0; methodIndex < cf.methods.size(); ++methodIndex) { - auto& method = cf.methods[methodIndex]; - if (utf8Text(cf, method.nameCp) != name || - utf8Text(cf, method.descCp) != descriptor) - continue; - if (support::ensureCode(cf, method) == nullptr) break; - auto promoted = jbc::ir::promoteMethod(*held, methodIndex); - if (promoted.is_err()) break; - const jbc::ir::CodeUnit& code = *promoted.unwrap(); - const auto& order = code.dom.order(); - for (ca::usize index = 0; index < order.size(); ++index) { - const jbc::ir::Insn* insn = code.dom.peek(order[index]); - if (insn == nullptr) continue; - if (insn->op != jbc::core::Op::INVOKEVIRTUAL && - insn->op != jbc::core::Op::INVOKEINTERFACE && - insn->op != jbc::core::Op::INVOKESPECIAL && - insn->op != jbc::core::Op::INVOKESTATIC) - continue; - jbc::ir::ClassSession::MemberSem sem; - if (!held->memberOf(*insn, sem) || sem.owner.empty()) continue; - const std::string semOwner{sem.owner}; - const std::string semName{sem.name}; - const std::string semDesc{sem.desc}; - const std::string declaredTarget = semOwner + "." + semName + semDesc; - bool expanded = false; - if (insn->op == jbc::core::Op::INVOKEVIRTUAL || - insn->op == jbc::core::Op::INVOKEINTERFACE) { - const mjbase::ClassKey targetKey = - analysis.hierarchy->findClass(semOwner); - if (targetKey.valid()) { - std::vector candidates{targetKey}; - for (const auto descendant : - analysis.hierarchy->descendants(targetKey)) - candidates.push_back(descendant); - for (const auto candidate : candidates) { - if (!analysis.declaresMethod(candidate.id, semName, semDesc)) - continue; - expanded = true; - result.push_back(referenceInfo( - {owner, name, descriptor, static_cast(index)}, - std::string{analysis.hierarchy->nameOf(candidate)} + "." + - semName + semDesc, - "resolved_override")); - } - } - } - if (!expanded) { - result.push_back(referenceInfo( - {owner, name, descriptor, static_cast(index)}, - declaredTarget, "direct")); - } - } - break; - } - } - annotateSupportingSources(result); - sortReferences(result); - return ca::core::Ok(std::move(result)); - } - catch (const std::exception& exception) { - return ca::core::Err(error(WorkspaceError::Internal, "list callees", exception.what())); - } -} - -WorkspaceResult WorkspaceBase::classRelations( - const std::string& internalName) const -{ - WorkspaceErrorInfo status = ensureLoaded("get class relations"); - if (status.is_err()) return ca::core::Err(std::move(status)); - if (internalName.empty()) { - return ca::core::Err( - error(WorkspaceError::InvalidArgument, "get class relations", "class is empty")); - } - try { - auto graph = analysisGraph(); - if (graph.is_err()) return ca::core::Err(std::move(graph).unwrap_err()); - auto analysis = std::move(graph).unwrap(); - const mjbase::ClassKey index = analysis.hierarchy->findClass(internalName); - if (!index.valid()) - return ca::core::Err( - error(WorkspaceError::NotFound, "get class relations", internalName)); - - ClassRelations result; - // super 链:ancestors 含起点(superChainOf 的公开面),跳过自身;合成节点使链以 - // java/lang/Object 收尾。 - const auto chain = analysis.hierarchy->ancestors(index); - for (ca::usize at = 1; at < chain.size(); ++at) { - result.superclasses.push_back(std::string{analysis.hierarchy->nameOf(chain[at])}); - } - // 接口闭包:super 链各类的直接接口 + 传递超接口(照旧 ancestors∩interface 语义)。 - for (const auto ancestor : analysis.hierarchy->interfaceClosure(index)) { - if (ancestor == index || !analysis.hierarchy->isInterface(ancestor)) continue; - result.interfaces.push_back(std::string{analysis.hierarchy->nameOf(ancestor)}); - } - for (const auto candidate : analysis.hierarchy->registrationOrder()) { - if (candidate == index) continue; - if (analysis.hierarchy->superClass(candidate) == index) { - result.directSubclasses.push_back( - std::string{analysis.hierarchy->nameOf(candidate)}); - } - if (analysis.hierarchy->isSubtype(candidate, index)) { - if (analysis.hierarchy->isInterface(index) && - !analysis.hierarchy->isInterface(candidate)) { - result.implementors.push_back( - std::string{analysis.hierarchy->nameOf(candidate)}); - } - if (!analysis.hierarchy->isInterface(index)) { - result.allSubclasses.push_back( - std::string{analysis.hierarchy->nameOf(candidate)}); - } - } - } - sortUnique(result.superclasses); - sortUnique(result.interfaces); - sortUnique(result.directSubclasses); - sortUnique(result.allSubclasses); - sortUnique(result.implementors); - - std::set relatedClasses{internalName}; - relatedClasses.insert(result.superclasses.begin(), result.superclasses.end()); - relatedClasses.insert(result.interfaces.begin(), result.interfaces.end()); - relatedClasses.insert(result.directSubclasses.begin(), result.directSubclasses.end()); - relatedClasses.insert(result.allSubclasses.begin(), result.allSubclasses.end()); - relatedClasses.insert(result.implementors.begin(), result.implementors.end()); - const auto supportingClasses = supportingClassIds(); - for (const auto& name : relatedClasses) { - const auto source = supportingClasses.find(name); - result.sources.push_back({name, - source != supportingClasses.end(), - source == supportingClasses.end() ? std::string{} - : source->second}); - } - return ca::core::Ok(std::move(result)); - } - catch (const std::exception& exception) { - return ca::core::Err( - error(WorkspaceError::Internal, "get class relations", exception.what())); - } -} - -WorkspaceResult> WorkspaceBase::overridesOfMethod( - const std::string& owner, const std::string& name, const std::string& descriptor) const -{ - WorkspaceErrorInfo status = ensureLoaded("get method overrides"); - if (status.is_err()) return ca::core::Err(std::move(status)); - if (owner.empty() || name.empty() || descriptor.empty()) { - return ca::core::Err(error(WorkspaceError::InvalidArgument, "get method overrides", - "owner, name and descriptor are required")); - } - try { - auto graph = analysisGraph(); - if (graph.is_err()) return ca::core::Err(std::move(graph).unwrap_err()); - auto analysis = std::move(graph).unwrap(); - const mjbase::ClassKey ownerKey = analysis.hierarchy->findClass(owner); - if (!ownerKey.valid()) - return ca::core::Err(error(WorkspaceError::NotFound, "get method overrides", owner)); - if (!analysis.declaresMethod(ownerKey.id, name, descriptor)) { - return ca::core::Err(error(WorkspaceError::NotFound, "get method overrides", - owner + "." + name + descriptor)); - } - - // 覆盖组件(mjbase 面直算):候选 = 全部声明同名同描述符方法的登记类; - // 两候选之间是子类型关系(任一方向)即连通;并查集闭包给出组件。 - // 与引擎共用的组件解析设施由主线 renamer 接管。 - std::vector candidates; - for (const auto& [id, summary] : analysis.summaries) { - (void)summary; - if (analysis.declaresMethod(id, name, descriptor)) - candidates.push_back(mjbase::ClassKey{id}); - } - std::sort(candidates.begin(), candidates.end(), - [](const mjbase::ClassKey& left, const mjbase::ClassKey& right) { - return left.id < right.id; - }); - std::unordered_map slotOf; - for (ca::usize at = 0; at < candidates.size(); ++at) - slotOf.emplace(candidates[at].id, at); - std::vector parent(candidates.size()); - for (ca::usize at = 0; at < parent.size(); ++at) parent[at] = at; - const std::function find = [&](ca::usize node) -> ca::usize { - return parent[node] == node ? node : parent[node] = find(parent[node]); - }; - for (ca::usize i = 0; i < candidates.size(); ++i) { - for (ca::usize j = i + 1; j < candidates.size(); ++j) { - if (analysis.hierarchy->isSubtype(candidates[i], candidates[j]) || - analysis.hierarchy->isSubtype(candidates[j], candidates[i])) { - parent[find(i)] = find(j); - } - } - } - const ca::usize ownerRoot = find(slotOf.at(ownerKey.id)); - - const auto supportingClasses = supportingClassIds(); - std::vector result; - for (ca::usize at = 0; at < candidates.size(); ++at) { - if (find(at) != ownerRoot) continue; - const auto methodOwner = std::string{analysis.hierarchy->nameOf(candidates[at])}; - const auto support = supportingClasses.find(methodOwner); - result.push_back({methodOwner, - name, - descriptor, - methodOwner == owner, - support != supportingClasses.end(), - support == supportingClasses.end() ? std::string{} : support->second}); - } - std::sort(result.begin(), result.end(), [](const MethodOverrideInfo& left, - const MethodOverrideInfo& right) { - if (left.owner != right.owner) return left.owner < right.owner; - if (left.name != right.name) return left.name < right.name; - return left.descriptor < right.descriptor; - }); - return ca::core::Ok(std::move(result)); - } - catch (const std::exception& exception) { - return ca::core::Err( - error(WorkspaceError::Internal, "get method overrides", exception.what())); - } -} - -jasm::Result WorkspaceBase::getJasm(const std::string& uri) const -{ - if (!isLoaded()) return jasmFailure("workspace is not loaded"); - jasm::ResourceJasmService service(*working_); - return service.disassemble(uri); -} - -// ── 变更事务面 ────────────────────────────────────────────────────────── - -MutationResult WorkspaceBase::validateJasm(const std::string& uri, - ca::str::Utf16StringRef sourceName, - ca::str::Utf16StringRef source, - const MutationRequest& request) -{ - WorkspaceErrorInfo status = ensureLoaded("validate JASM"); - if (status.is_err()) return stateFailure(std::move(status)); - if (!accepts(request)) return revisionConflict(); - // 选择器与结构化补丁同口径:裸内部名按容器字节内容解析到规范 URI。 - auto resolvedUri = resolveClassUri(*working_, uri); - if (resolvedUri.is_err()) return stateFailure(std::move(resolvedUri).unwrap_err()); - try { - auto candidate = cloneWorking(); - ClassMutationService service(*candidate); - auto assembled = service.applyJasm(std::move(resolvedUri).unwrap(), - sourceName, source, - request.validationMode); - - MutationResult result; - result.revision = revision_; - result.exportable = canExport(); - result.diagnostics = std::move(assembled.diagnostics); - if (!assembled.isOk()) { - result.status = error(WorkspaceError::InvalidArgument, "validate JASM", - "candidate class did not pass validation"); - return result; - } - result.status = WorkspaceErrorInfo{}; - result.byteDiffs = diffContainers(*working_, *candidate); - result.affectedClasses = changedClassNames(*candidate, result.byteDiffs); - result.affectedReferences = collectImpacts(*candidate, result.affectedClasses); - return result; - } - catch (const std::exception& exception) { - return stateFailure(error(WorkspaceError::Internal, "validate JASM", exception.what())); - } -} - -MutationResult WorkspaceBase::commit(std::unique_ptr candidate, - std::vector diagnostics, - const MutationRequest& request) -{ - if (!accepts(request)) return revisionConflict(); - auto diffs = diffContainers(*working_, *candidate); - - MutationResult result; - result.status = WorkspaceErrorInfo{}; - result.diagnostics = std::move(diagnostics); - result.exportable = true; - result.byteDiffs = std::move(diffs); - if (result.byteDiffs.empty()) { - // 空 diff 不产生新 revision、不进历史(照旧)。 - result.revision = revision_; - return result; - } - - //-- 台账事务装配(RFC §6.2:uri 字节级 entries,before/after digest 链)---- - std::set uris; - working_->visitResources( - [&](std::string_view uri, const model::IResource&) { uris.insert(std::string(uri)); }); - candidate->visitResources( - [&](std::string_view uri, const model::IResource&) { uris.insert(std::string(uri)); }); - - try { - project::LedgerAppendInput input; - input.label = request.label; - input.producedBy = "mjbase-workspace/1"; - input.createdAt = project::rfc3339UtcNow(); - const ca::u64 txnId = ledger_.nextTxnId(); - ca::u32 seq = 0; - for (const auto& uri : uris) { - const model::IResource* beforeResource = working_->getResource(uri); - const model::IResource* afterResource = candidate->getResource(uri); - std::vector beforeBytes; - std::vector afterBytes; - if (beforeResource != nullptr) { - auto bytes = bytesOf(*beforeResource); - if (bytes.is_err()) - return stateFailure(std::move(bytes).unwrap_err()); - beforeBytes = std::move(bytes).unwrap(); - } - if (afterResource != nullptr) { - auto bytes = bytesOf(*afterResource); - if (bytes.is_err()) return stateFailure(std::move(bytes).unwrap_err()); - afterBytes = std::move(bytes).unwrap(); - } - if (beforeBytes == afterBytes) continue; - - project::LedgerEntry entry; - entry.uri = project::copyLedgerText(uri); - if (afterResource == nullptr) { - entry.op = project::LedgerEntryOp::Remove; - entry.before = project::ledgerDigestOfBytes(beforeBytes.data(), - beforeBytes.size()); - } - else { - entry.after = project::ledgerDigestOfBytes(afterBytes.data(), afterBytes.size()); - entry.op = beforeResource == nullptr ? project::LedgerEntryOp::Add - : project::LedgerEntryOp::Replace; - if (beforeResource != nullptr) { - entry.before = project::ledgerDigestOfBytes(beforeBytes.data(), - beforeBytes.size()); - } - entry.payloadRef = project::overlaySectionName(txnId, seq++); - input.payloads.push_back({entry.payloadRef.clone(), afterBytes}); - } - input.entries.push_back(std::move(entry)); - } - auto appended = ledger_.append(input); - if (appended.is_err()) return stateFailure(ledgerFailure("commit", std::move(appended).unwrap_err())); - - result.affectedClasses = changedClassNames(*candidate, result.byteDiffs); - result.affectedReferences = collectImpacts(*candidate, result.affectedClasses); - redoHint_.clear(); - working_ = std::move(candidate); - ++revision_; - result.revision = revision_; - result.committed = true; - return result; - } - catch (const std::exception& exception) { - return stateFailure(error(WorkspaceError::Internal, "commit", exception.what())); - } -} - -MutationResult WorkspaceBase::applyJasm(const std::string& uri, - ca::str::Utf16StringRef sourceName, - ca::str::Utf16StringRef source, - const MutationRequest& request) -{ - if (request.dryRun) return validateJasm(uri, sourceName, source, request); - WorkspaceErrorInfo status = ensureLoaded("apply JASM"); - if (status.is_err()) return stateFailure(std::move(status)); - if (!accepts(request)) return revisionConflict(); - // 选择器与结构化补丁同口径:裸内部名按容器字节内容解析到规范 URI。 - auto resolvedUri = resolveClassUri(*working_, uri); - if (resolvedUri.is_err()) return stateFailure(std::move(resolvedUri).unwrap_err()); - try { - auto candidate = cloneWorking(); - ClassMutationService service(*candidate); - auto assembled = service.applyJasm(std::move(resolvedUri).unwrap(), - sourceName, source, - request.validationMode); - if (!assembled.isOk()) { - MutationResult result; - result.status = error(WorkspaceError::InvalidArgument, "apply JASM", - "candidate class did not pass validation"); - result.revision = revision_; - result.exportable = canExport(); - result.diagnostics = std::move(assembled.diagnostics); - return result; - } - return commit(std::move(candidate), std::move(assembled.diagnostics), request); - } - catch (const std::exception& exception) { - return stateFailure(error(WorkspaceError::Internal, "apply JASM", exception.what())); - } -} - -MutationResult WorkspaceBase::validateClassBytes(const std::string& uri, - const std::vector& classBytes, - const MutationRequest& request) -{ - WorkspaceErrorInfo status = ensureLoaded("validate class bytes"); - if (status.is_err()) return stateFailure(std::move(status)); - if (!accepts(request)) return revisionConflict(); - if (classBytes.empty()) { - return stateFailure(error(WorkspaceError::InvalidArgument, "validate class bytes", - "candidate class is empty")); - } - try { - auto candidate = cloneWorking(); - const auto current = - ca::core::tag_cast::dyn_cast(candidate->getResource(uri)); - if (current == nullptr) { - return stateFailure(error(WorkspaceError::NotFound, "validate class bytes", - "class resource not found: " + uri)); - } - candidate->replaceResource(uri, - std::make_unique(uri, classBytes)); - - MutationResult result; - result.revision = revision_; - result.exportable = canExport(); - // 门禁作用域 = 本次替换的类。 - result.status = validateCandidate(*candidate, result.diagnostics, - request.validationMode, {uri}); - if (result.status.is_err()) return result; - result.byteDiffs = diffContainers(*working_, *candidate); - result.affectedClasses = changedClassNames(*candidate, result.byteDiffs); - result.affectedReferences = collectImpacts(*candidate, result.affectedClasses); - return result; - } - catch (const std::exception& exception) { - return stateFailure( - error(WorkspaceError::Internal, "validate class bytes", exception.what())); - } -} - -MutationResult WorkspaceBase::applyClassBytes(const std::string& uri, - const std::vector& classBytes, - const MutationRequest& request) -{ - if (request.dryRun) return validateClassBytes(uri, classBytes, request); - WorkspaceErrorInfo status = ensureLoaded("apply class bytes"); - if (status.is_err()) return stateFailure(std::move(status)); - if (!accepts(request)) return revisionConflict(); - if (classBytes.empty()) { - return stateFailure(error(WorkspaceError::InvalidArgument, "apply class bytes", - "candidate class is empty")); - } - try { - auto candidate = cloneWorking(); - const auto current = - ca::core::tag_cast::dyn_cast(candidate->getResource(uri)); - if (current == nullptr) { - return stateFailure(error(WorkspaceError::NotFound, "apply class bytes", - "class resource not found: " + uri)); - } - candidate->replaceResource(uri, - std::make_unique(uri, classBytes)); - std::vector diagnostics; - // 门禁作用域 = 本次替换的类。 - status = validateCandidate(*candidate, diagnostics, request.validationMode, {uri}); - if (status.is_err()) { - MutationResult result; - result.status = std::move(status); - result.revision = revision_; - result.exportable = canExport(); - result.diagnostics = std::move(diagnostics); - return result; - } - return commit(std::move(candidate), std::move(diagnostics), request); - } - catch (const std::exception& exception) { - return stateFailure( - error(WorkspaceError::Internal, "apply class bytes", exception.what())); - } -} - -MutationResult WorkspaceBase::validateClassMutation(const ClassMutation& mutation, - const MutationRequest& request) -{ - WorkspaceErrorInfo status = ensureLoaded("validate class mutation"); - if (status.is_err()) return stateFailure(std::move(status)); - if (!accepts(request)) return revisionConflict(); - try { - auto candidate = cloneWorking(); - ClassMutationService service(*candidate); - auto outcome = service.apply(mutation, request.validationMode); - - MutationResult result; - result.revision = revision_; - result.exportable = canExport(); - result.diagnostics = std::move(outcome.diagnostics); - if (outcome.status.is_err()) { - result.status = std::move(outcome.status); - return result; - } - result.status = WorkspaceErrorInfo{}; - result.byteDiffs = diffContainers(*working_, *candidate); - result.affectedClasses = changedClassNames(*candidate, result.byteDiffs); - result.affectedReferences = collectImpacts(*candidate, result.affectedClasses); - return result; - } - catch (const std::exception& exception) { - return stateFailure( - error(WorkspaceError::Internal, "validate class mutation", exception.what())); - } -} - -MutationResult WorkspaceBase::applyClassMutation(const ClassMutation& mutation, - const MutationRequest& request) -{ - WorkspaceErrorInfo status = ensureLoaded("apply class mutation"); - if (status.is_err()) return stateFailure(std::move(status)); - //选择器契约对齐 replaceClassBytes:精确资源 URI 直接命中,内部名按字节 - //内容解析到规范 URI。 - auto resolvedUri = resolveClassUri(*working_, mutation.classUri); - if (resolvedUri.is_err()) return stateFailure(std::move(resolvedUri).unwrap_err()); - ClassMutation resolvedMutation = mutation; - resolvedMutation.classUri = std::move(resolvedUri).unwrap(); - if (request.dryRun) return validateClassMutation(resolvedMutation, request); - if (!accepts(request)) return revisionConflict(); - try { - auto candidate = cloneWorking(); - ClassMutationService service(*candidate); - auto outcome = service.apply(resolvedMutation, request.validationMode); - if (outcome.status.is_err()) { - MutationResult result; - result.status = std::move(outcome.status); - result.revision = revision_; - result.exportable = canExport(); - result.diagnostics = std::move(outcome.diagnostics); - return result; - } - return commit(std::move(candidate), std::move(outcome.diagnostics), request); - } - catch (const std::exception& exception) { - return stateFailure( - error(WorkspaceError::Internal, "apply class mutation", exception.what())); - } -} - -MutationResult WorkspaceBase::replaceClassBytes(const std::string& uriOrInternalName, - const std::vector& bytes, - const MutationRequest& request) -{ - WorkspaceErrorInfo status = ensureLoaded("replace class bytes"); - if (status.is_err()) return stateFailure(std::move(status)); - if (!accepts(request)) return revisionConflict(); - if (bytes.empty() || bytes.size() > kMaximumClassReplacementSize) - return stateFailure(error(WorkspaceError::InvalidArgument, "replace class bytes", - "class size must be in 1..16777216 bytes")); - if (bytes.size() < 4 || bytes[0] != 0xca || bytes[1] != 0xfe || bytes[2] != 0xba || - bytes[3] != 0xbe) - return stateFailure(error(WorkspaceError::InvalidArgument, "replace class bytes", - "candidate does not start with CAFEBABE")); - - auto resolved = resolveClassUri(*working_, uriOrInternalName); - if (resolved.is_err()) return stateFailure(std::move(resolved).unwrap_err()); - const auto uri = std::move(resolved).unwrap(); - const auto expectedInternalName = mjbase::model::internalNameFromClassUri(uri); - if (expectedInternalName.empty()) - return stateFailure(error(WorkspaceError::InvalidArgument, "replace class bytes", - "target URI is not a canonical class or multi-release class " - "URI")); - const auto* current = - ca::core::tag_cast::dyn_cast(working_->getResource(uri)); - if (current == nullptr) - return stateFailure(error(WorkspaceError::NotFound, "replace class bytes", uri)); - - try { - const auto actualInternalName = internalNameOfBytes(bytes); - if (actualInternalName != expectedInternalName) - return stateFailure(error(WorkspaceError::InvalidArgument, "replace class bytes", - "candidate internal name does not match target URI")); - - auto candidate = cloneWorking(); - candidate->replaceResource( - uri, std::make_unique(locationText(*current), bytes)); - std::vector diagnostics; - // 门禁作用域 = 本次替换的类。 - status = validateCandidate(*candidate, diagnostics, request.validationMode, {uri}); - if (status.is_err()) { - MutationResult result = stateFailure(std::move(status)); - result.diagnostics = std::move(diagnostics); - return result; - } - if (!request.dryRun) - return commit(std::move(candidate), std::move(diagnostics), request); - - MutationResult result; - result.status = WorkspaceErrorInfo{}; - result.revision = revision_; - result.exportable = canExport(); - result.diagnostics = std::move(diagnostics); - result.byteDiffs = diffContainers(*working_, *candidate); - result.affectedClasses = changedClassNames(*candidate, result.byteDiffs); - result.affectedReferences = collectImpacts(*candidate, result.affectedClasses); - return result; - } - catch (const std::exception& exception) { - return stateFailure( - error(WorkspaceError::InvalidArgument, "replace class bytes", exception.what())); - } -} - -WorkspaceResult> WorkspaceBase::modifiedClasses() const -{ - WorkspaceErrorInfo status = ensureLoaded("list modified classes"); - if (status.is_err()) return ca::core::Err(std::move(status)); - try { - std::vector result; - for (const auto& diff : diffContainers(*original_, *working_)) { - const auto* original = ca::core::tag_cast::dyn_cast( - original_->getResource(diff.uri)); - const auto* current = ca::core::tag_cast::dyn_cast( - working_->getResource(diff.uri)); - if (original == nullptr && current == nullptr) continue; - - ModifiedClassInfo item; - item.resourceUri = diff.uri; - if (original != nullptr) { - item.originalSize = original->get().size(); - item.originalFingerprint = fingerprintBytes(original->get()); - item.originalInternalName = internalNameOfBytes(original->get()); - } - if (current != nullptr) { - item.currentSize = current->get().size(); - item.currentFingerprint = fingerprintBytes(current->get()); - item.currentInternalName = internalNameOfBytes(current->get()); - } - result.push_back(std::move(item)); - } - return ca::core::Ok(std::move(result)); - } - catch (const std::exception& exception) { - return ca::core::Err( - error(WorkspaceError::Internal, "list modified classes", exception.what())); - } -} - -/// @brief 候选容器 → 层次图即时重建(校验门用;resource_jasm_service 同款容 -/// 错口径):只读 Class 骨架,畸形/重复/环条目跳过。容器内类可解析走 -/// 子类型判定;外部/JDK 类型不进图——relaxed 语义按不可解析处置。 -void buildValidationHierarchy(const model::ResourceContainer& container, - mjbase::hierarchy::ClassHierarchy& hierarchy, mjbase::KeyPool& pool) -{ - container.visitResources([&](std::string_view, const model::IResource& resource) { - const auto* classResource = - ca::core::tag_cast::dyn_cast(&resource); - if (classResource == nullptr) return; - auto read = jbc::core::readClass(jbc::core::ByteView(classResource->get())); - if (read.is_err()) return; - auto parsed = std::move(read).unwrap(); - const auto& file = *parsed.file; - - mjbase::hierarchy::ClassSummary summary; - summary.self = mjbase::classKeyOf(pool, mjbase::jasm::classFileUtf8ToString(file.thisName())); - const std::string_view superName = file.superName(); - summary.super = superName.empty() - ? mjbase::ClassKey{} - : mjbase::classKeyOf(pool, mjbase::jasm::classFileUtf8ToString(superName)); - for (const ca::u16 ifaceCp : file.interfaces) { - summary.interfaces.push_back(mjbase::classKeyOf( - pool, mjbase::jasm::classFileUtf8ToString(file.className(ifaceCp)))); - } - (void)hierarchy.addClass(summary); - }); -} - -WorkspaceErrorInfo WorkspaceBase::validateCandidate(const model::ResourceContainer& candidate, - std::vector& diagnostics, - jasm::ValidationMode mode, - const std::vector& scopeUris) const -{ - // off:跳过门禁(直通零诊断)。 - if (mode == jasm::ValidationMode::Off) return WorkspaceErrorInfo{}; - jasm::JvmJasmService validator; - // 层次图按候选容器即时重建:relaxed 下容器内类走子类型判定,外部 - // /JDK 类型不可解析不判非法。 - mjbase::KeyPool pool; - mjbase::hierarchy::ClassHierarchy hierarchy{pool}; - buildValidationHierarchy(candidate, hierarchy, pool); - jasm::JvmJasmContext context; - context.hierarchy = &hierarchy; - context.validationMode = mode; - - // per-class 聚合缓冲:类 uri → 计数 + 截断明细,不再逐方法刷屏。 - struct ClassAggregate - { - std::string internalName; - ca::usize errors = 0; - ca::usize warnings = 0; - std::vector details; ///< 截断明细(前 3 条) - }; - std::map aggregates; - - WorkspaceErrorInfo status; - for (const auto& uri : scopeUris) { - const auto* classResource = - ca::core::tag_cast::dyn_cast(candidate.getResource(uri)); - if (classResource == nullptr) continue; // 作用域内已删除条目(修复移除等) - // spi sanitize gate:引用语义损坏的 class 在候选入口前置拒绝(协作面, - // workspace × sanitize gate 契约落点)。 - const auto issues = spi::sanitizeClassFile(classResource->get()); - if (!issues.empty()) { - for (const auto& issue : issues) { - diagnostics.push_back({jasm::DiagnosticSeverity::Error, - jasm::DiagnosticStage::EditGate, - utf16("sanitize: " + issue.message.to_std_string()), - std::nullopt}); - } - return error(WorkspaceError::InvalidArgument, "validate workspace candidate", - "class failed sanitize gate: " + uri); - } - auto validated = validator.validate(classResource->get(), context); - if (validated.diagnostics.empty()) continue; - auto& aggregate = aggregates[uri]; - aggregate.internalName = internalNameOfBytes(classResource->get()); - for (const auto& diagnostic : validated.diagnostics) { - if (diagnostic.severity == jasm::DiagnosticSeverity::Error) ++aggregate.errors; - else - ++aggregate.warnings; - if (aggregate.details.size() < 3) aggregate.details.push_back(diagnostic.message.clone()); - } - } - for (auto& entry : aggregates) { - auto& aggregate = entry.second; - const bool failing = aggregate.errors > 0; - const auto& label = - aggregate.internalName.empty() ? entry.first : aggregate.internalName; - std::string message = "class " + label + ": " + std::to_string(aggregate.errors) + - " error(s), " + std::to_string(aggregate.warnings) + " warning(s)"; - if (!aggregate.details.empty()) { - message += - "\n " + aggregate.details[0].to_utf8_string().to_std_string(); - if (aggregate.details.size() > 1) - message += - "\n " + aggregate.details[1].to_utf8_string().to_std_string(); - if (aggregate.details.size() > 2) - message += - "\n " + aggregate.details[2].to_utf8_string().to_std_string(); - const ca::usize total = aggregate.errors + aggregate.warnings; - if (total > aggregate.details.size()) - message += "\n ... and " + std::to_string(total - aggregate.details.size()) + - " more"; - } - diagnostics.push_back({failing ? jasm::DiagnosticSeverity::Error - : jasm::DiagnosticSeverity::Warning, - jasm::DiagnosticStage::EditGate, utf16(message), std::nullopt}); - if (failing && status.is_ok()) - status = error(WorkspaceError::InvalidArgument, "validate workspace candidate", - "class did not pass validation: " + entry.first); - } - return status; -} - -// ── workflow 事务助手(错误通道 WorkspaceErrorInfo)────────────── - -void removeWorkflowExport(const WorkflowResult& result) -{ - if (!result.exported || result.exportPath.empty()) return; - std::error_code filesystemError; - std::filesystem::remove(fsPath(result.exportPath), filesystemError); -} - -WorkspaceResult createWorkflowCandidateJarPath() -{ - namespace fs = std::filesystem; - static std::atomic sequence{0}; - std::error_code filesystemError; - const auto root = fs::temp_directory_path(filesystemError); - if (filesystemError) { - return ca::core::Err(error(WorkspaceError::Internal, "workflow candidate artifact", - filesystemError.message())); - } - for (ca::usize attempt = 0; attempt < 16; ++attempt) { - const auto id = std::to_string( - std::chrono::steady_clock::now().time_since_epoch().count()) + - "-" + std::to_string(sequence.fetch_add(1)); - const auto path = root / ("mjbase-workflow-candidate-" + id + ".jar"); - if (!fs::exists(path)) return ca::core::Ok(path); - } - return ca::core::Err(error(WorkspaceError::AlreadyExists, "workflow candidate artifact", - "cannot allocate a unique temporary JAR")); -} - -/// RAII 守卫:作用域结束删除候选 JAR 快照(一次性消费契约的兜底清理面)。 -class ScopedWorkflowCandidateJar final -{ -public: - explicit ScopedWorkflowCandidateJar(std::filesystem::path path) - : path_(std::move(path)) - {} - - ~ScopedWorkflowCandidateJar() - { - std::error_code filesystemError; - std::filesystem::remove(path_, filesystemError); - } - - ScopedWorkflowCandidateJar(const ScopedWorkflowCandidateJar&) = delete; - ScopedWorkflowCandidateJar& operator=(const ScopedWorkflowCandidateJar&) = delete; - - const std::filesystem::path& path() const noexcept { return path_; } - -private: - std::filesystem::path path_; -}; - -WorkspaceErrorInfo validateWorkflowReferenceQuery(const WorkflowReferenceQuery& query) -{ - if (query.owner.empty()) { - return error(WorkspaceError::InvalidArgument, "workflow xref", "owner is required"); - } - if (query.kind == WorkflowReferenceKind::Class) return WorkspaceErrorInfo{}; - if (query.name.empty() || query.descriptor.empty()) { - return error(WorkspaceError::InvalidArgument, "workflow xref", - "member name and descriptor are required"); - } - return WorkspaceErrorInfo{}; -} - -/// @brief 对任意容器(工作副本或候选)执行精确 xref 查询。 -/// -/// 渲染口径与 workspace 自有 xref 族一致("type"/"direct",owner.name:desc 拼接); -/// 不走 analysisScanContainer——候选容器是独立的纯容器,无 supporting 叠加面。 -WorkspaceResult> workflowReferences( - const model::ResourceContainer& container, const WorkflowReferenceQuery& query) -{ - const WorkspaceErrorInfo status = validateWorkflowReferenceQuery(query); - if (status.is_err()) return ca::core::Err(status); - try { - ReferenceScan scan; - scan.build(container); - std::vector result; - switch (query.kind) { - case WorkflowReferenceKind::Class: - for (const auto& reference : scan.typeReferencesTo(query.owner)) { - result.push_back(referenceInfo(reference.site, reference.targetType, "type")); - } - break; - case WorkflowReferenceKind::Field: - for (const auto& reference : - scan.fieldReferencesTo(query.owner, query.name, query.descriptor)) { - result.push_back(referenceInfo( - reference.site, - reference.target.owner + "." + reference.target.name + - reference.target.descriptor, - "direct")); - } - break; - case WorkflowReferenceKind::Method: - for (const auto& reference : - scan.methodReferencesTo(query.owner, query.name, query.descriptor)) { - result.push_back(referenceInfo( - reference.site, - reference.target.owner + "." + reference.target.name + - reference.target.descriptor, - "direct")); - } - break; - } - sortReferences(result); - return ca::core::Ok(std::move(result)); - } - catch (const std::exception& exception) { - return ca::core::Err(error(WorkspaceError::Internal, "workflow xref", exception.what())); - } -} - -/// @brief 扫描 JDK 归档(.jar/.zip/.jmod,递归)建层次图。 -/// -/// mbase CommonSuperClassProvider::scan 的 mjbase 对应物:resolver 的数据获取 -/// 是调用方的事,管线只消费已建好的图(pass-contract §9)。逐归档逐类 -/// fail-open——解析不了的归档/类不贡献层次事实(mbase 扫描口径一致),层次 -/// 缺口由 CommonSuperClassResolver 的回退路径兜住。jimage(lib/modules)不是 -/// zip 容器,不在扫描集(与 mbase 行为一致)。 -void scanJdkHierarchy(const std::string& jdkPath, mjbase::hierarchy::ClassHierarchy& hierarchy, - mjbase::KeyPool& pool) -{ - namespace fs = std::filesystem; - std::error_code filesystemError; - fs::recursive_directory_iterator it(fsPath(jdkPath), fs::directory_options::skip_permission_denied, - filesystemError); - if (filesystemError) return; - for (const fs::directory_entry& entry = *it; it != fs::end(it); it.increment(filesystemError)) { - if (filesystemError) break; - if (!entry.is_regular_file(filesystemError) || filesystemError) { - filesystemError.clear(); - continue; - } - std::string extension = entry.path().extension().string(); - std::transform(extension.begin(), extension.end(), extension.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - if (extension != ".jar" && extension != ".zip" && extension != ".jmod") continue; - - model::ResourceContainer archive; - auto imported = model::ResourceHelper::importFromJar(archive, pathToUtf8(entry.path())); - if (imported.is_err()) continue; // 单归档 fail-open - for (const auto& scanned : analysis::scanClassResources(archive)) { - auto summary = hierarchy::fillSummary(*scanned.session, pool); - (void)hierarchy.addClass(std::move(summary)); // 单类 fail-open(环检查拒绝等) - } - } -} - -// ── remap / rename / pipeline / workflow 方法族 ───────────────────────── - -WorkspaceErrorInfo WorkspaceBase::runPipeline(model::ResourceContainer& candidate, - const PipelineRequest& request) const -{ - if (!request.pipeline.empty() && !request.passNames.empty()) { - return error(WorkspaceError::InvalidArgument, "run pipeline", - "pipeline and passNames are mutually exclusive"); - } - - spi::registerBuiltinPipelines(); - spi::PipelineBuilder builder; - if (!request.pipeline.empty()) { - if (!spi::PassRegistry::instance().hasPipeline(request.pipeline)) { - return error(WorkspaceError::NotFound, "run pipeline", request.pipeline); - } - builder = spi::PassRegistry::instance().getPipeline(request.pipeline); - } - else if (!request.passNames.empty()) { - for (const auto& name : request.passNames) { - if (name.empty() || spi::applyTo(spi::defaultPassRegistry(), name, builder).is_err()) { - return error(WorkspaceError::NotFound, "run pass", name); - } - } - } - else { - builder = spi::PassRegistry::instance().getPipeline("default-optimize"); - } - - // jdkPath 非空:扫描 JDK 归档建层次图,Strict resolver 接入帧重算。 - // resolver 链的生命周期必须覆盖 - // buildNestedPipeline + runContainerPipeline 全程,故在 job 之前构造。 - mjbase::KeyPool jdkPool; - mjbase::hierarchy::ClassHierarchy jdkHierarchy{jdkPool}; - mjbase::spi::CommonSuperClassResolver superResolver{jdkHierarchy}; - mjbase::spi::StrictTypeResolverAdapter typeResolver{superResolver}; - spi::PassPipelineOptions options; - if (!request.jdkPath.empty()) { - std::error_code filesystemError; - if (!std::filesystem::is_directory(fsPath(request.jdkPath), filesystemError) || - filesystemError) { - return error(WorkspaceError::NotFound, "run pipeline", "JDK path does not exist"); - } - scanJdkHierarchy(request.jdkPath, jdkHierarchy, jdkPool); - options.typeResolver = &typeResolver; - } - - spi::PassJobContext job; - spi::GlobalPassManager pipeline = builder.buildNestedPipeline(job, nullptr, nullptr, options); - auto ran = spi::runContainerPipeline(candidate, pipeline, job); - if (ran.is_err()) { - auto pipelineError = std::move(ran).unwrap_err(); - return error(WorkspaceError::Internal, "run pipeline", - std::string(spi::to_string(pipelineError.stage)) + " " + - pipelineError.target + ": " + pipelineError.detail); - } - return WorkspaceErrorInfo{}; -} - -ImpactAnalysis WorkspaceBase::analyzeRemap(const asm_remapper::MemberRemapPlan& plan, - const asm_remapper::RemapPolicy& policy) const -{ - ImpactAnalysis result; - auto loaded = ensureLoaded("analyze remap"); - if (loaded.is_err()) { - result.status = std::move(loaded); - return result; - } - try { - auto candidate = cloneWorking(); - auto remapped = asm_remapper::applyResourceRemapPlan(*candidate, plan, policy); - if (remapped.is_err()) { - result.status = error(WorkspaceError::Internal, "analyze remap", - std::string("remap failed: ") + - asm_remapper::to_string(std::move(remapped).unwrap_err())); - return result; - } - // 门禁作用域 = remap 改动的类;diff 只算一次,scope 与报告共用。 - const auto byteDiffs = diffContainers(*working_, *candidate); - result.status = - validateCandidate(*candidate, result.diagnostics, jasm::ValidationMode::Relaxed, - changedUris(byteDiffs)); - if (result.status.is_err()) return result; - result.byteDiffs = std::move(byteDiffs); - const auto beforeClasses = changedClassNames(*working_, result.byteDiffs); - const auto afterClasses = changedClassNames(*candidate, result.byteDiffs); - result.beforeReferences = collectImpacts(*working_, beforeClasses); - result.afterReferences = collectImpacts(*candidate, afterClasses); - for (const auto& diff : result.byteDiffs) { - if (diff.uri.find(".class") == std::string::npos) - result.risks.push_back("resource changed: " + diff.uri); - } - result.exportable = true; - return result; - } - catch (const std::exception& exception) { - result.status = error(WorkspaceError::Internal, "analyze remap", exception.what()); - return result; - } -} - -MutationResult WorkspaceBase::applyRemap(const asm_remapper::MemberRemapPlan& plan, - const asm_remapper::RemapPolicy& policy, - const MutationRequest& request) -{ - auto loaded = ensureLoaded("apply remap"); - if (loaded.is_err()) return stateFailure(std::move(loaded)); - if (!accepts(request)) return revisionConflict(); - if (request.dryRun) { - auto analysis = analyzeRemap(plan, policy); - MutationResult result; - result.status = std::move(analysis.status); - result.revision = revision_; - result.exportable = analysis.exportable; - result.diagnostics = std::move(analysis.diagnostics); - result.byteDiffs = std::move(analysis.byteDiffs); - result.affectedReferences = std::move(analysis.afterReferences); - result.affectedClasses = changedClassNames(*working_, result.byteDiffs); - return result; - } - try { - auto candidate = cloneWorking(); - auto remapped = asm_remapper::applyResourceRemapPlan(*candidate, plan, policy); - if (remapped.is_err()) { - return stateFailure(error(WorkspaceError::Internal, "apply remap", - std::string("remap failed: ") + - asm_remapper::to_string(std::move(remapped).unwrap_err()))); - } - std::vector diagnostics; - // 门禁作用域 = remap 改动的类。 - const auto byteDiffs = diffContainers(*working_, *candidate); - auto status = validateCandidate(*candidate, diagnostics, request.validationMode, - changedUris(byteDiffs)); - if (status.is_err()) { - MutationResult result; - result.status = std::move(status); - result.revision = revision_; - result.exportable = canExport(); - result.diagnostics = std::move(diagnostics); - return result; - } - return commit(std::move(candidate), std::move(diagnostics), request); - } - catch (const std::exception& exception) { - return stateFailure(error(WorkspaceError::Internal, "apply remap", exception.what())); - } -} - -MutationResult WorkspaceBase::validatePipeline(const PipelineRequest& pipeline, - const MutationRequest& request) -{ - auto loaded = ensureLoaded("validate pipeline"); - if (loaded.is_err()) return stateFailure(std::move(loaded)); - if (!accepts(request)) return revisionConflict(); - try { - auto candidate = cloneWorking(); - auto status = runPipeline(*candidate, pipeline); - if (status.is_err()) return stateFailure(std::move(status)); - - MutationResult result; - result.revision = revision_; - result.exportable = canExport(); - // 门禁作用域 = pipeline 改动的类;diff 只算一次,scope 与报告共用。 - const auto byteDiffs = diffContainers(*working_, *candidate); - result.status = - validateCandidate(*candidate, result.diagnostics, request.validationMode, - changedUris(byteDiffs)); - if (result.status.is_err()) return result; - result.byteDiffs = std::move(byteDiffs); - result.affectedClasses = changedClassNames(*candidate, result.byteDiffs); - result.affectedReferences = collectImpacts(*candidate, result.affectedClasses); - return result; - } - catch (const std::exception& exception) { - return stateFailure(error(WorkspaceError::Internal, "validate pipeline", exception.what())); - } -} - -MutationResult WorkspaceBase::applyPipeline(const PipelineRequest& pipeline, - const MutationRequest& request) -{ - if (request.dryRun) return validatePipeline(pipeline, request); - auto loaded = ensureLoaded("apply pipeline"); - if (loaded.is_err()) return stateFailure(std::move(loaded)); - if (!accepts(request)) return revisionConflict(); - try { - auto candidate = cloneWorking(); - auto status = runPipeline(*candidate, pipeline); - if (status.is_err()) return stateFailure(std::move(status)); - - std::vector diagnostics; - // 门禁作用域 = pipeline 改动的类。 - const auto byteDiffs = diffContainers(*working_, *candidate); - status = validateCandidate(*candidate, diagnostics, request.validationMode, - changedUris(byteDiffs)); - if (status.is_err()) { - MutationResult result; - result.status = std::move(status); - result.revision = revision_; - result.exportable = canExport(); - result.diagnostics = std::move(diagnostics); - return result; - } - return commit(std::move(candidate), std::move(diagnostics), request); - } - catch (const std::exception& exception) { - return stateFailure(error(WorkspaceError::Internal, "apply pipeline", exception.what())); - } -} - -WorkflowResult WorkspaceBase::runWorkflow(const WorkflowRequest& workflow, - const MutationRequest& request) -{ - WorkflowResult result; - const auto loaded = ensureLoaded("run workflow"); - if (loaded.is_err()) { - result.mutation = stateFailure(std::move(loaded)); - return result; - } - if (!accepts(request)) { - result.mutation = revisionConflict(); - return result; - } - - const auto fail = [&](const std::string& stage, WorkspaceErrorInfo status) { - result.stages.push_back({stage, status}); - result.mutation = stateFailure(std::move(status)); - return std::move(result); - }; - - try { - auto inputFingerprint = fingerprintContainer(*working_); - if (inputFingerprint.is_err()) - return fail("fingerprint", std::move(inputFingerprint).unwrap_err()); - result.inputFingerprint = std::move(inputFingerprint).unwrap(); - - auto candidate = cloneWorking(); - if (workflow.remapPlan != nullptr) { - auto remapped = asm_remapper::applyResourceRemapPlan(*candidate, *workflow.remapPlan, - workflow.remapPolicy); - if (remapped.is_err()) { - return fail("mapping", - error(WorkspaceError::Internal, "run workflow mapping", - std::string("remap failed: ") + - asm_remapper::to_string(std::move(remapped).unwrap_err()))); - } - result.stages.push_back({"mapping", WorkspaceErrorInfo{}}); - } - if (workflow.runPipeline) { - const auto status = runPipeline(*candidate, workflow.pipeline); - if (status.is_err()) return fail("pipeline", status); - result.stages.push_back({"pipeline", WorkspaceErrorInfo{}}); - } - - std::vector diagnostics; - // 门禁作用域 = workflow 改动的类。 - const auto byteDiffs = diffContainers(*working_, *candidate); - const auto validation = - validateCandidate(*candidate, diagnostics, jasm::ValidationMode::Relaxed, - changedUris(byteDiffs)); - if (validation.is_err()) { - result.stages.push_back({"validation", validation}); - result.mutation.status = validation; - result.mutation.revision = revision_; - result.mutation.exportable = canExport(); - result.mutation.diagnostics = std::move(diagnostics); - return result; - } - result.stages.push_back({"validation", WorkspaceErrorInfo{}}); - - for (const auto& query : workflow.referenceQueries) { - auto before = workflowReferences(*working_, query); - if (before.is_err()) return fail("xref", std::move(before).unwrap_err()); - auto after = workflowReferences(*candidate, query); - if (after.is_err()) return fail("xref", std::move(after).unwrap_err()); - result.references.push_back( - {query, std::move(before).unwrap(), std::move(after).unwrap()}); - } - if (!workflow.referenceQueries.empty()) - result.stages.push_back({"xref", WorkspaceErrorInfo{}}); - - auto candidateFingerprint = fingerprintContainer(*candidate); - if (candidateFingerprint.is_err()) - return fail("fingerprint", std::move(candidateFingerprint).unwrap_err()); - result.candidateFingerprint = std::move(candidateFingerprint).unwrap(); - - if (workflow.candidateJarConsumer) { - auto artifactPath = createWorkflowCandidateJarPath(); - if (artifactPath.is_err()) - return fail("candidate_artifact", std::move(artifactPath).unwrap_err()); - ScopedWorkflowCandidateJar artifact(std::move(artifactPath).unwrap()); - const auto exportStatus = - exportContainerToJar(*candidate, sourcePath_, pathToUtf8(artifact.path()), revision_); - if (exportStatus.is_err()) return fail("candidate_artifact", exportStatus); - const auto stageStatus = workflow.candidateJarConsumer(pathToUtf8(artifact.path())); - if (stageStatus.is_err()) { - return fail(workflow.candidateJarStageName.empty() ? "candidate_artifact" - : workflow.candidateJarStageName, - stageStatus); - } - result.stages.push_back({workflow.candidateJarStageName.empty() - ? "candidate_artifact" - : workflow.candidateJarStageName, - WorkspaceErrorInfo{}}); - } - - if (!workflow.exportPath.empty()) { - auto destination = validateExportDestination(sourcePath_, workflow.exportPath); - if (destination.is_err()) return fail("export", std::move(destination).unwrap_err()); - result.exportPath = pathToUtf8(destination.unwrap()); - if (!request.dryRun) { - const auto exportStatus = - exportContainerToJar(*candidate, sourcePath_, result.exportPath, revision_); - if (exportStatus.is_err()) return fail("export", exportStatus); - result.exported = true; - } - result.stages.push_back( - {request.dryRun ? "export_preview" : "export", WorkspaceErrorInfo{}}); - } - - if (request.dryRun) { - result.mutation.status = WorkspaceErrorInfo{}; - result.mutation.revision = revision_; - result.mutation.exportable = canExport(); - result.mutation.diagnostics = std::move(diagnostics); - result.mutation.byteDiffs = diffContainers(*working_, *candidate); - result.mutation.affectedClasses = changedClassNames(*candidate, result.mutation.byteDiffs); - result.mutation.affectedReferences = - collectImpacts(*candidate, result.mutation.affectedClasses); - return result; - } - result.mutation = commit(std::move(candidate), std::move(diagnostics), request); - if (result.mutation.status.is_err()) { - removeWorkflowExport(result); - result.exported = false; - } - return result; - } - catch (const std::exception& exception) { - removeWorkflowExport(result); - result.exported = false; - return fail("workflow", error(WorkspaceError::Internal, "run workflow", exception.what())); - } -} - -WorkspaceErrorInfo WorkspaceBase::buildRenamePlan(const RenameRequest& request, - asm_remapper::MemberRemapPlan& plan, - std::vector& mappings, - std::vector& overrides, - std::vector& risks) const -{ - if (request.owner.empty() || request.newName.empty()) - return error(WorkspaceError::InvalidArgument, "build rename plan", - "owner and new name are required"); - switch (request.kind) { - case RenameKind::Class: - { - auto source = classInfo(request.owner); - if (source.is_err()) return source.unwrap_err(); - auto existing = classInfo(request.newName); - if (existing.is_ok() && request.newName != request.owner) - return error(WorkspaceError::AlreadyExists, "rename class", request.newName); - plan.mapClass(request.owner, request.newName); - mappings.push_back({"class", request.owner, request.newName}); - return WorkspaceErrorInfo{}; - } - case RenameKind::Package: - { - auto classes = listClasses(); - if (classes.is_err()) return classes.unwrap_err(); - std::string oldPrefix = request.owner; - std::string newPrefix = request.newName; - while (!oldPrefix.empty() && oldPrefix.back() == '/') oldPrefix.pop_back(); - while (!newPrefix.empty() && newPrefix.back() == '/') newPrefix.pop_back(); - for (const auto& clazz : classes.unwrap()) { - if (clazz.internalName != oldPrefix && - clazz.internalName.rfind(oldPrefix + "/", 0) != 0) - continue; - const auto suffix = clazz.internalName.substr(oldPrefix.size()); - const auto mapped = newPrefix + suffix; - plan.mapClass(clazz.internalName, mapped); - mappings.push_back({"class", clazz.internalName, mapped}); - } - if (mappings.empty()) return error(WorkspaceError::NotFound, "rename package", request.owner); - return WorkspaceErrorInfo{}; - } - case RenameKind::Field: - { - if (request.name.empty() || request.descriptor.empty()) - return error(WorkspaceError::InvalidArgument, "rename field", - "name and descriptor are required"); - auto field = fieldInfo(request.owner, request.name, request.descriptor); - if (field.is_err()) return field.unwrap_err(); - plan.mapField(request.owner, request.name, request.descriptor, request.owner, - request.newName, request.descriptor); - mappings.push_back({"field", - request.owner + "." + request.name + request.descriptor, - request.owner + "." + request.newName + request.descriptor}); - return WorkspaceErrorInfo{}; - } - case RenameKind::Method: - { - if (request.name.empty() || request.descriptor.empty()) - return error(WorkspaceError::InvalidArgument, "rename method", - "name and descriptor are required"); - auto method = methodInfo(request.owner, request.name, request.descriptor); - if (method.is_err()) return method.unwrap_err(); - auto component = overridesOfMethod(request.owner, request.name, request.descriptor); - if (component.is_err()) return component.unwrap_err(); - overrides = std::move(component).unwrap(); - const bool hasSupporting = - std::any_of(overrides.begin(), overrides.end(), - [](const auto& item) { return item.supporting; }); - if (overrides.size() > 1 && !request.cascadeOverrides) - risks.push_back("method belongs to an override component; set cascadeOverrides " - "after reviewing mappings"); - if (hasSupporting) - risks.push_back("override component contains supporting-library declarations that " - "cannot be renamed in this workspace"); - for (const auto& item : overrides) { - if (item.supporting) continue; - plan.mapMethod(item.owner, item.name, item.descriptor, item.owner, request.newName, - item.descriptor); - mappings.push_back({"method", - item.owner + "." + item.name + item.descriptor, - item.owner + "." + request.newName + item.descriptor}); - } - return WorkspaceErrorInfo{}; - } - case RenameKind::Local: - if (request.methodName.empty() || request.name.empty() || request.descriptor.empty()) - return error(WorkspaceError::InvalidArgument, "rename local", - "method name, local name and descriptor are required"); - { - auto method = methodInfo(request.owner, request.methodName, request.descriptor); - if (method.is_err()) return method.unwrap_err(); - } - mappings.push_back({"local", request.name, request.newName}); - return WorkspaceErrorInfo{}; - } - return error(WorkspaceError::InvalidArgument, "build rename plan", "unknown rename kind"); -} - -RenameAnalysis WorkspaceBase::analyzeRename(const RenameRequest& request) const -{ - RenameAnalysis result; - asm_remapper::MemberRemapPlan plan; - result.status = buildRenamePlan(request, plan, result.mappings, result.overrideComponent, - result.impact.risks); - if (result.status.is_err()) { - result.impact.status = result.status; - return result; - } - if (request.kind == RenameKind::Local) { - result.impact.status = WorkspaceErrorInfo{}; - result.impact.exportable = canExport(); - result.readyToApply = true; - return result; - } - auto renameRisks = std::move(result.impact.risks); - result.impact = analyzeRemap(plan, request.policy); - result.impact.risks.insert(result.impact.risks.end(), - std::make_move_iterator(renameRisks.begin()), - std::make_move_iterator(renameRisks.end())); - result.status = result.impact.status; - result.requiresOverrideReview = !result.impact.risks.empty(); - result.readyToApply = result.status.is_ok() && !result.requiresOverrideReview; - return result; -} - -MutationResult WorkspaceBase::applyRename(const RenameRequest& request, - const MutationRequest& requestOptions) -{ - auto preview = analyzeRename(request); - if (preview.status.is_err()) return stateFailure(std::move(preview.status)); - if (!preview.readyToApply) { - return stateFailure(error(WorkspaceError::FailedPrecondition, "apply rename", - "rename requires override review: " + - (preview.impact.risks.empty() - ? std::string("unknown risk") - : preview.impact.risks.front()))); - } - if (request.kind == RenameKind::Local) { - ClassMutation mutation; - mutation.classUri = request.owner; - mutation.kind = ClassMutationKind::RenameLocal; - mutation.member = {request.methodName, request.descriptor}; - mutation.localName = request.name; - mutation.replacementLocalName = request.newName; - mutation.localIndex = request.localIndex; - return applyClassMutation(mutation, requestOptions); - } - asm_remapper::MemberRemapPlan plan; - std::vector mappings; - std::vector overrides; - std::vector risks; - auto status = buildRenamePlan(request, plan, mappings, overrides, risks); - if (status.is_err()) return stateFailure(std::move(status)); - return applyRemap(plan, request.policy, requestOptions); -} - -WorkflowCandidateCommitResult WorkspaceBase::commitWorkflowCandidateJar( - const WorkflowCandidateCommitRequest& candidateRequest, const MutationRequest& request) -{ - WorkflowCandidateCommitResult result; - WorkspaceErrorInfo loaded = ensureLoaded("commit workflow candidate JAR"); - if (loaded.is_err()) { - result.mutation = stateFailure(std::move(loaded)); - return result; - } - if (!accepts(request)) { - result.mutation = revisionConflict(); - return result; - } - if (candidateRequest.candidateJarPath.empty()) { - result.mutation = - stateFailure(error(WorkspaceError::InvalidArgument, "commit workflow candidate JAR", - "candidate JAR path is empty")); - return result; - } - - const auto removeExport = [&result] { - if (!result.exported || result.exportPath.empty()) return; - std::error_code filesystemError; - std::filesystem::remove(result.exportPath, filesystemError); - result.exported = false; - }; - - try { - std::error_code filesystemError; - if (!std::filesystem::is_regular_file(candidateRequest.candidateJarPath, filesystemError) || - filesystemError) { - result.mutation = - stateFailure(error(WorkspaceError::NotFound, "commit workflow candidate JAR", - candidateRequest.candidateJarPath)); - return result; - } - - auto candidate = std::make_unique(); - auto imported = - model::ResourceHelper::importFromJar(*candidate, candidateRequest.candidateJarPath); - if (imported.is_err()) { - result.mutation = stateFailure( - modelFailure("commit workflow candidate JAR", std::move(imported).unwrap_err())); - return result; - } - - std::vector diagnostics; - // 门禁作用域 = 候选 JAR 相对工作副本的改动面。 - const auto byteDiffs = diffContainers(*working_, *candidate); - const WorkspaceErrorInfo validation = - validateCandidate(*candidate, diagnostics, jasm::ValidationMode::Relaxed, - changedUris(byteDiffs)); - if (validation.is_err()) { - result.mutation.status = validation; - result.mutation.revision = revision_; - result.mutation.exportable = canExport(); - result.mutation.diagnostics = std::move(diagnostics); - return result; - } - - if (!candidateRequest.exportPath.empty()) { - auto destination = - validateExportDestination(sourcePath_, candidateRequest.exportPath); - if (destination.is_err()) { - result.mutation = stateFailure(std::move(destination).unwrap_err()); - return result; - } - result.exportPath = pathToUtf8(destination.unwrap()); - if (!request.dryRun) { - const WorkspaceErrorInfo exportStatus = - exportContainerToJar(*candidate, sourcePath_, result.exportPath, revision_); - if (exportStatus.is_err()) { - result.mutation = stateFailure(exportStatus); - return result; - } - result.exported = true; - } - } - - if (request.dryRun) { - result.mutation.status = WorkspaceErrorInfo{}; - result.mutation.revision = revision_; - result.mutation.exportable = canExport(); - result.mutation.diagnostics = std::move(diagnostics); - result.mutation.byteDiffs = diffContainers(*working_, *candidate); - result.mutation.affectedClasses = - changedClassNames(*candidate, result.mutation.byteDiffs); - result.mutation.affectedReferences = - collectImpacts(*candidate, result.mutation.affectedClasses); - return result; - } - - result.mutation = commit(std::move(candidate), std::move(diagnostics), request); - if (result.mutation.status.is_err()) removeExport(); - return result; - } - catch (const std::exception& exception) { - removeExport(); - result.mutation = stateFailure(error(WorkspaceError::Internal, - "commit workflow candidate JAR", exception.what())); - return result; - } -} - -JarDiffResult WorkspaceBase::diffJar(const std::string& referencePath) const -{ - JarDiffResult result; - result.referencePath = referencePath; - WorkspaceErrorInfo loaded = ensureLoaded("diff JAR"); - if (loaded.is_err()) { - result.status = std::move(loaded); - return result; - } - if (referencePath.empty()) { - result.status = - error(WorkspaceError::InvalidArgument, "diff JAR", "reference path is empty"); - return result; - } - - try { - model::ResourceContainer reference; - auto imported = model::ResourceHelper::importFromJar(reference, referencePath); - if (imported.is_err()) { - result.status = modelFailure("diff JAR", std::move(imported).unwrap_err()); - return result; - } - result.byteDiffs = diffContainers(*working_, reference); - for (const auto& diff : result.byteDiffs) { - const model::IResource* workspaceResource = working_->getResource(diff.uri); - const model::IResource* referenceResource = reference.getResource(diff.uri); - if (workspaceResource == nullptr) { - ++result.referenceOnly; - } - else if (referenceResource == nullptr) { - ++result.workspaceOnly; - } - else { - ++result.modified; - } - } - result.status = WorkspaceErrorInfo{}; - return result; - } - catch (const std::exception& exception) { - result.status = error(WorkspaceError::DataLoss, "diff JAR", exception.what()); - return result; - } -} - -JarRepairResult WorkspaceBase::analyzeJarRepair(const JarRepairRequest& repair, - const MutationRequest& request) const -{ - JarRepairResult result; - WorkspaceErrorInfo loaded = ensureLoaded("analyze JAR repair"); - if (loaded.is_err()) { - result.mutation = stateFailure(std::move(loaded)); - return result; - } - if (!accepts(request)) { - result.mutation = revisionConflict(); - return result; - } - - try { - auto candidate = cloneWorking(); - result.resourceCount = candidate->size(); - result.issues = scanJarRepairIssues(*candidate, repair, result.classCount); - result.mutation.revision = revision_; - result.mutation.exportable = canExport(); - if (!hasSelectedRepairIssues(result.issues)) return result; - removeSelectedRepairIssues(*candidate, result.issues); - - // 门禁作用域 = 修复改动面:删除型修复的改动条目已不在候选中, - // 其余条目未动不参与门禁(基线类编辑前就存在)。 - const auto byteDiffs = diffContainers(*working_, *candidate); - result.mutation.status = - validateCandidate(*candidate, result.mutation.diagnostics, - jasm::ValidationMode::Relaxed, changedUris(byteDiffs)); - if (result.mutation.status.is_err()) return result; - result.mutation.byteDiffs = std::move(byteDiffs); - result.mutation.affectedClasses = - changedClassNames(*candidate, result.mutation.byteDiffs); - result.mutation.affectedReferences = - collectImpacts(*candidate, result.mutation.affectedClasses); - return result; - } - catch (const std::exception& exception) { - result.mutation = - stateFailure(error(WorkspaceError::Internal, "analyze JAR repair", exception.what())); - return result; - } -} - -JarRepairResult WorkspaceBase::applyJarRepair(const JarRepairRequest& repair, - const MutationRequest& request) -{ - if (request.dryRun) return analyzeJarRepair(repair, request); - JarRepairResult result; - WorkspaceErrorInfo loaded = ensureLoaded("apply JAR repair"); - if (loaded.is_err()) { - result.mutation = stateFailure(std::move(loaded)); - return result; - } - if (!accepts(request)) { - result.mutation = revisionConflict(); - return result; - } - - try { - auto candidate = cloneWorking(); - result.resourceCount = candidate->size(); - result.issues = scanJarRepairIssues(*candidate, repair, result.classCount); - if (!hasSelectedRepairIssues(result.issues)) { - result.mutation.revision = revision_; - result.mutation.exportable = canExport(); - return result; - } - removeSelectedRepairIssues(*candidate, result.issues); - - std::vector diagnostics; - // 门禁作用域 = 修复改动面:删除型修复的改动条目已不在候选中, - // 其余条目未动不参与门禁。 - const auto byteDiffs = diffContainers(*working_, *candidate); - const WorkspaceErrorInfo status = validateCandidate( - *candidate, diagnostics, request.validationMode, changedUris(byteDiffs)); - if (status.is_err()) { - result.mutation.status = status; - result.mutation.revision = revision_; - result.mutation.exportable = canExport(); - result.mutation.diagnostics = std::move(diagnostics); - return result; - } - result.mutation = commit(std::move(candidate), std::move(diagnostics), request); - return result; - } - catch (const std::exception& exception) { - result.mutation = - stateFailure(error(WorkspaceError::Internal, "apply JAR repair", exception.what())); - return result; - } -} - -MutationResult WorkspaceBase::revertClass(const std::string& uri, const MutationRequest& request) -{ - WorkspaceErrorInfo status = ensureLoaded("revert class"); - if (status.is_err()) return stateFailure(std::move(status)); - if (!accepts(request)) return revisionConflict(); - const auto* original = - ca::core::tag_cast::dyn_cast(original_->getResource(uri)); - const auto* current = - ca::core::tag_cast::dyn_cast(working_->getResource(uri)); - if (original == nullptr || current == nullptr) { - return stateFailure(error(WorkspaceError::NotFound, "revert class", uri)); - } - try { - auto candidate = cloneWorking(); - candidate->replaceResource(uri, - std::make_unique( - locationText(*original), original->get())); - std::vector diagnostics; - // 门禁作用域 = 本次还原的类——undo/revert 自救路径不再被容器 - // 基线类的既存形态卡死。 - status = validateCandidate(*candidate, diagnostics, request.validationMode, {uri}); - if (status.is_err()) { - MutationResult result = stateFailure(std::move(status)); - result.diagnostics = std::move(diagnostics); - return result; - } - if (!request.dryRun) - return commit(std::move(candidate), std::move(diagnostics), request); - - MutationResult result; - result.status = WorkspaceErrorInfo{}; - result.revision = revision_; - result.exportable = canExport(); - result.diagnostics = std::move(diagnostics); - result.byteDiffs = diffContainers(*working_, *candidate); - result.affectedClasses = changedClassNames(*candidate, result.byteDiffs); - result.affectedReferences = collectImpacts(*candidate, result.affectedClasses); - return result; - } - catch (const std::exception& exception) { - return stateFailure(error(WorkspaceError::Internal, "revert class", exception.what())); - } -} - -WorkspaceResult WorkspaceBase::createSavepoint(const std::string& name) -{ - WorkspaceErrorInfo status = ensureLoaded("create savepoint"); - if (status.is_err()) return ca::core::Err(std::move(status)); - if (name.empty()) - return ca::core::Err( - error(WorkspaceError::InvalidArgument, "create savepoint", "name is empty")); - // 台账换底:savepoint = 书签(零字节命名指针,§4)。savepoint 时机查自动 - // 压缩阈值(尽力而为:失败不影响 savepoint 本身)。 - auto bookmarked = ledger_.setBookmark(name, project::rfc3339UtcNow()); - if (bookmarked.is_err()) - return ca::core::Err(ledgerFailure("create savepoint", std::move(bookmarked).unwrap_err())); - autoCompactLedger(); - return ca::core::Ok(); -} - -MutationResult WorkspaceBase::restoreSavepoint(const std::string& name, - const MutationRequest& request) -{ - WorkspaceErrorInfo status = ensureLoaded("restore savepoint"); - if (status.is_err()) return stateFailure(std::move(status)); - if (!accepts(request)) return revisionConflict(); - const auto* bookmark = ledger_.bookmark(name); - if (bookmark == nullptr) - return stateFailure(error(WorkspaceError::NotFound, "restore savepoint", name)); - try { - // 台账换底:restore = 物化书签事务位状态再走一次普通提交(记一条反向 - // 事务,undo 窗不受损;书签可位于头之前或之后——undo/redo 皆可越过)。 - auto candidate = cloneWorking(); - const auto restored = materializeState(candidate, bookmark->txn, "restore savepoint"); - if (restored.is_err()) return stateFailure(restored); - return commit(std::move(candidate), {}, request); - } - catch (const std::exception& exception) { - return stateFailure( - error(WorkspaceError::Internal, "restore savepoint", exception.what())); - } -} - -MutationResult WorkspaceBase::undo(const MutationRequest& request) -{ - WorkspaceErrorInfo status = ensureLoaded("undo"); - if (status.is_err()) return stateFailure(std::move(status)); - if (!accepts(request)) return revisionConflict(); - if (!ledger_.canUndo()) - return stateFailure( - error(WorkspaceError::FailedPrecondition, "undo", "history is empty")); - try { - auto before = cloneWorking(); - const ca::u64 head = ledger_.headTxn(); - const auto* txn = ledger_.transaction(head); - const WorkspaceErrorInfo applied = - inverseApplyLedgerTransaction(*before, *txn, "undo"); - if (applied.is_err()) return stateFailure(applied); - auto moved = ledger_.moveHead(txn->parent); - if (moved.is_err()) return stateFailure(ledgerFailure("undo", std::move(moved).unwrap_err())); - redoHint_.push_back(head); - auto diffs = diffContainers(*working_, *before); - working_ = std::move(before); - ++revision_; - - MutationResult result; - result.status = WorkspaceErrorInfo{}; - result.revision = revision_; - result.committed = true; - result.exportable = true; - result.byteDiffs = std::move(diffs); - result.affectedClasses = changedClassNames(*working_, result.byteDiffs); - result.affectedReferences = collectImpacts(*working_, result.affectedClasses); - return result; - } - catch (const std::exception& exception) { - return stateFailure(error(WorkspaceError::Internal, "undo", exception.what())); - } -} - -MutationResult WorkspaceBase::redo(const MutationRequest& request) -{ - WorkspaceErrorInfo status = ensureLoaded("redo"); - if (status.is_err()) return stateFailure(std::move(status)); - if (!accepts(request)) return revisionConflict(); - if (redoHint_.empty()) - return stateFailure( - error(WorkspaceError::FailedPrecondition, "redo", "history is empty")); - try { - const ca::u64 target = redoHint_.back(); - const auto* txn = ledger_.transaction(target); - if (txn == nullptr || txn->parent != ledger_.headTxn()) - return stateFailure( - error(WorkspaceError::Internal, "redo", "redo target is no longer reachable")); - auto before = cloneWorking(); - const auto applied = applyLedgerTransaction(*before, *txn, "redo"); - if (applied.is_err()) return stateFailure(applied); - auto moved = ledger_.moveHead(target); - if (moved.is_err()) return stateFailure(ledgerFailure("redo", std::move(moved).unwrap_err())); - redoHint_.pop_back(); - auto diffs = diffContainers(*working_, *before); - working_ = std::move(before); - ++revision_; - - MutationResult result; - result.status = WorkspaceErrorInfo{}; - result.revision = revision_; - result.committed = true; - result.exportable = true; - result.byteDiffs = std::move(diffs); - result.affectedClasses = changedClassNames(*working_, result.byteDiffs); - result.affectedReferences = collectImpacts(*working_, result.affectedClasses); - return result; - } - catch (const std::exception& exception) { - return stateFailure(error(WorkspaceError::Internal, "redo", exception.what())); - } -} - -// ── 导出面 ────────────────────────────────────────────────────────────── - -WorkspaceResult WorkspaceBase::exportJar(const std::string& outputPath) const -{ - WorkspaceErrorInfo status = ensureLoaded("export JAR"); - if (status.is_err()) return ca::core::Err(std::move(status)); - if (artifactKind_ != ArtifactKind::Jar) { - return ca::core::Err(error(WorkspaceError::FailedPrecondition, "export JAR", - "workspace artifact is not a JAR")); - } - // close/export 前 flush 挂点(§3.2):导出物离开内存前台账定稿。未绑定 = - // no-op;flush 失败 fail-closed(不产出一个后台无账的导出物)。 - if (ledger_.bound() && ledger_.hasPending()) { - auto flushed = ledger_.flush(project::rfc3339UtcNow()); - if (flushed.is_err()) - return ca::core::Err(ledgerFailure("export JAR", std::move(flushed).unwrap_err())); - } - const WorkspaceErrorInfo exported = - exportContainerToJar(*working_, sourcePath_, outputPath, revision_); - if (exported.is_err()) return ca::core::Err(std::move(exported)); - return ca::core::Ok(); -} - -WorkspaceResult WorkspaceBase::exportClass(const std::string& outputPath) const -{ - WorkspaceErrorInfo status = ensureLoaded("export class"); - if (status.is_err()) return ca::core::Err(std::move(status)); - if (artifactKind_ != ArtifactKind::Class || primaryClassUri_.empty()) { - return ca::core::Err(error(WorkspaceError::FailedPrecondition, "export class", - "workspace artifact is not a class")); - } - if (ledger_.bound() && ledger_.hasPending()) { - auto flushed = ledger_.flush(project::rfc3339UtcNow()); - if (flushed.is_err()) - return ca::core::Err(ledgerFailure("export class", std::move(flushed).unwrap_err())); - } - const WorkspaceErrorInfo exported = - exportContainerToClass(*working_, primaryClassUri_, sourcePath_, outputPath, revision_); - if (exported.is_err()) return ca::core::Err(std::move(exported)); - return ca::core::Ok(); -} - -std::vector WorkspaceBase::supportingResources() const -{ - std::vector result; - for (const auto& support : supporting_) { - SupportingResourceInfo info; - info.id = support.id; - info.path = support.path; - info.fingerprint = support.fingerprint; - info.resourceCount = support.container->size(); - info.role = support.role; - support.container->visitResources([&](std::string_view, const model::IResource& resource) { - if (resource.getType() == model::ResourceType::Class) ++info.classCount; - }); - result.push_back(std::move(info)); - } - return result; -} - -// ── 台账换底:引擎接线与重放辅助 ────────────────────────────────── - -WorkspaceErrorInfo WorkspaceBase::ledgerFailure(const std::string& operation, - const project::ProjectStoreError& cause) const -{ - WorkspaceErrorInfo failure; - switch (cause.kind) { - case project::ProjectStoreErrorKind::Open: failure.code = WorkspaceError::NotFound; break; - case project::ProjectStoreErrorKind::Lock: - failure.code = WorkspaceError::PermissionDenied; - break; - case project::ProjectStoreErrorKind::Section: - case project::ProjectStoreErrorKind::Schema: - failure.code = WorkspaceError::DataLoss; - break; - case project::ProjectStoreErrorKind::Io: failure.code = WorkspaceError::Internal; break; - } - failure.message = operation + ": " + cause.message.to_std_string(); - return failure; -} - -WorkspaceResult WorkspaceBase::ledgerBaseDigest() const -{ - if (original_ == nullptr) { - return ca::core::Err( - error(WorkspaceError::Internal, "ledger digest", "no container loaded")); - } - try { - std::vector uris; - original_->visitResources( - [&](std::string_view uri, const model::IResource&) { uris.push_back(std::string(uri)); }); - std::sort(uris.begin(), uris.end()); - project::ContainerDigestBuilder builder; - for (const auto& uri : uris) { - const auto* resource = original_->getResource(uri); - if (resource == nullptr) continue; - auto bytes = bytesOf(*resource); - if (bytes.is_err()) return ca::core::Err(std::move(bytes).unwrap_err()); - const auto& content = bytes.unwrap(); - builder.add(uri, content.data(), content.size()); - } - return ca::core::Ok(builder.finish().to_std_string()); - } - catch (const std::exception& exception) { - return ca::core::Err( - error(WorkspaceError::Internal, "ledger digest", exception.what())); - } -} - -std::unique_ptr WorkspaceBase::resourceFromBytes(const std::string& uri, - std::vector bytes) -{ - if (uri == std::string(mjbase::jar::JarUtil::kManifestFileName)) { - return std::make_unique(model::ManifestResource::fromBytes(bytes)); - } - if (hasClassExtension(uri)) { - return std::make_unique(uri, std::move(bytes)); - } - return std::make_unique(uri, std::move(bytes)); -} - -WorkspaceResult> WorkspaceBase::replayBaseline() const -{ - if (!ledger_.hasBaselineSection()) return cloneContainer(*original_); - auto bytes = ledger_.baselineBytes(); - if (bytes.is_err()) - return ca::core::Err(ledgerFailure("replay baseline", std::move(bytes).unwrap_err())); - auto baseline = std::move(bytes).unwrap(); - auto container = std::make_unique(); - WorkspaceErrorInfo failure; - auto scanned = project::forEachContainerEntry( - reinterpret_cast(baseline.data()), baseline.size(), - [&](std::string_view uri, const ca::u8* payload, ca::usize payloadSize) { - container->addResource(uri, resourceFromBytes( - std::string(uri), - std::vector(payload, payload + payloadSize))); - return true; - }); - if (scanned.is_err()) { - failure = ledgerFailure("replay baseline", std::move(scanned).unwrap_err()); - return ca::core::Err(std::move(failure)); - } - return ca::core::Ok(std::move(container)); -} - -WorkspaceResult> WorkspaceBase::resolveBeforeBytes( - const project::LedgerTransaction& txn, const project::LedgerEntry& entry) const -{ - const std::string uri = entry.uri.to_std_string(); - std::vector bytes; - const auto last = ledger_.lastTouchOf(uri, txn.parent); - if (last != 0) { - const auto* touch = ledger_.transaction(last); - for (const auto& prior : touch->entries) { - if (!prior.uri.ref().equals(entry.uri.ref())) continue; - auto payload = ledger_.payloadBytes(prior.payloadRef); - if (payload.is_err()) - return ca::core::Err(ledgerFailure("resolve before bytes", - std::move(payload).unwrap_err())); - bytes = std::move(payload).unwrap(); - break; - } - } - else { - // 基线侧:压缩基线 section 或原始快照。 - if (ledger_.hasBaselineSection()) { - auto baseline = ledger_.baselineBytes(); - if (baseline.is_err()) - return ca::core::Err( - ledgerFailure("resolve before bytes", std::move(baseline).unwrap_err())); - auto baselineBytes = std::move(baseline).unwrap(); - std::vector found; - auto hit = project::findContainerEntry( - reinterpret_cast(baselineBytes.data()), baselineBytes.size(), uri, - found); - if (hit.is_err()) - return ca::core::Err(ledgerFailure("resolve before bytes", - std::move(hit).unwrap_err())); - if (hit.unwrap()) bytes = std::move(found); - } - else { - const auto* resource = original_ != nullptr ? original_->getResource(uri) : nullptr; - if (resource != nullptr) { - auto content = bytesOf(*resource); - if (content.is_err()) return ca::core::Err(std::move(content).unwrap_err()); - bytes = std::move(content).unwrap(); - } - } - } - // digest 自检:before 侧字节必须与台账记录一致(载荷/基线损毁当场暴露)。 - const auto digest = - project::ledgerDigestOfBytes(bytes.data(), bytes.size()); - if (!digest.ref().equals(entry.before.ref())) { - return ca::core::Err(error(WorkspaceError::DataLoss, "resolve before bytes", - "digest mismatch for '" + uri + "': ledger says " + - entry.before.to_std_string() + ", resolved " + - digest.to_std_string())); - } - return ca::core::Ok(std::move(bytes)); -} - -WorkspaceErrorInfo WorkspaceBase::applyLedgerTransaction( - model::ResourceContainer& container, const project::LedgerTransaction& txn, - const std::string& operation) const -{ - for (const auto& entry : txn.entries) { - const std::string uri = entry.uri.to_std_string(); - const bool exists = container.hasResource(uri); - if (entry.op == project::LedgerEntryOp::Add && exists) { - return error(WorkspaceError::DataLoss, operation, - "add entry but uri already exists: " + uri); - } - if (entry.op == project::LedgerEntryOp::Replace && !exists) { - return error(WorkspaceError::DataLoss, operation, - "replace entry but uri is missing: " + uri); - } - if (entry.op == project::LedgerEntryOp::Remove) { - if (!exists) - return error(WorkspaceError::DataLoss, operation, - "remove entry but uri is missing: " + uri); - auto& resources = container.getResources(); - resources.erase(std::string_view(uri)); - continue; - } - auto payload = ledger_.payloadBytes(entry.payloadRef); - if (payload.is_err()) return ledgerFailure(operation, std::move(payload).unwrap_err()); - auto bytes = std::move(payload).unwrap(); - const auto digest = project::ledgerDigestOfBytes(bytes.data(), bytes.size()); - if (!digest.ref().equals(entry.after.ref())) { - return error(WorkspaceError::DataLoss, operation, - "overlay digest mismatch for '" + uri + "': ledger says " + - entry.after.to_std_string() + ", payload is " + - digest.to_std_string()); - } - container.replaceResource(uri, resourceFromBytes(uri, std::move(bytes))); - } - return WorkspaceErrorInfo{}; -} - -WorkspaceErrorInfo WorkspaceBase::inverseApplyLedgerTransaction( - model::ResourceContainer& container, const project::LedgerTransaction& txn, - const std::string& operation) const -{ - for (const auto& entry : txn.entries) { - const std::string uri = entry.uri.to_std_string(); - const bool exists = container.hasResource(uri); - if (entry.op == project::LedgerEntryOp::Add) { - if (!exists) - return error(WorkspaceError::DataLoss, operation, - "undo add but uri is missing: " + uri); - auto& resources = container.getResources(); - resources.erase(std::string_view(uri)); - continue; - } - if (entry.op == project::LedgerEntryOp::Remove && exists) { - return error(WorkspaceError::DataLoss, operation, - "undo remove but uri already exists: " + uri); - } - if (entry.op == project::LedgerEntryOp::Replace && !exists) { - return error(WorkspaceError::DataLoss, operation, - "undo replace but uri is missing: " + uri); - } - auto before = resolveBeforeBytes(txn, entry); - if (before.is_err()) return std::move(before).unwrap_err(); - container.replaceResource(uri, - resourceFromBytes(uri, std::move(before).unwrap())); - } - return WorkspaceErrorInfo{}; -} - -WorkspaceErrorInfo WorkspaceBase::materializeState( - std::unique_ptr& candidate, ca::u64 txn, - const std::string& operation) const -{ - const ca::u64 head = ledger_.headTxn(); - if (txn == head) return WorkspaceErrorInfo{}; - if (txn > head) { - for (const ca::u64 step : ledger_.chainTo(txn)) { - if (step <= head) continue; - const auto* row = ledger_.transaction(step); - const auto applied = applyLedgerTransaction(*candidate, *row, operation); - if (applied.is_err()) return applied; - } - return WorkspaceErrorInfo{}; - } - auto chain = ledger_.chainTo(head); - for (auto it = chain.rbegin(); it != chain.rend(); ++it) { - if (*it <= txn) continue; - const auto* row = ledger_.transaction(*it); - const auto applied = inverseApplyLedgerTransaction(*candidate, *row, operation); - if (applied.is_err()) return applied; - } - return WorkspaceErrorInfo{}; -} - -void WorkspaceBase::autoCompactLedger() -{ - if (!ledger_.bound() || !ledger_.shouldAutoCompact()) return; - // 自动压缩(尽力而为):先落 pending(压缩前置),再按保留参数阈值推进; - // 任何失败留给下一次触发,不打断调用方主路径。 - if (ledger_.hasPending()) { - auto flushed = ledger_.flush(project::rfc3339UtcNow()); - if (flushed.is_err()) return; - } - const auto point = ledger_.retentionPoint(project::LedgerCompactRequest{}, true, nullptr); - if (point <= ledger_.baseTxn()) return; - try { - auto candidate = cloneWorking(); - const auto materialized = materializeState(candidate, point, "ledger compaction"); - if (materialized.is_err()) return; - std::vector>> entries; - std::vector uris; - candidate->visitResources( - [&](std::string_view uri, const model::IResource&) { uris.push_back(std::string(uri)); }); - std::sort(uris.begin(), uris.end()); - for (const auto& uri : uris) { - const auto* resource = candidate->getResource(uri); - if (resource == nullptr) continue; - auto bytes = bytesOf(*resource); - if (bytes.is_err()) return; - entries.emplace_back(project::copyLedgerText(uri), std::move(bytes).unwrap()); - } - (void)ledger_.compact(project::LedgerCompactRequest{}, - project::serializeContainerEntries(entries), - project::rfc3339UtcNow(), true); - } - catch (const std::exception&) { - return; - } -} - -WorkspaceResult WorkspaceBase::bindLedgerStore(const std::string& bundleDir) -{ - const WorkspaceErrorInfo loaded = ensureLoaded("bind ledger store"); - if (loaded.is_err()) return ca::core::Err(std::move(loaded)); - if (bundleDir.empty()) - return ca::core::Err( - error(WorkspaceError::InvalidArgument, "bind ledger store", "path is empty")); - auto digest = ledgerBaseDigest(); - if (digest.is_err()) return ca::core::Err(std::move(digest).unwrap_err()); - - // 采纳失败不破坏当前会话:先在旁路物化,成功才换底。 - const std::string previousDigest = ledger_.baseContainerDigest().to_std_string(); - auto opened = project::EditLedgerStore::openBundle( - project::copyLedgerText(bundleDir), project::copyLedgerText(digest.unwrap())); - if (opened.is_err()) - return ca::core::Err(ledgerFailure("bind ledger store", std::move(opened).unwrap_err())); - ledger_ = std::move(opened).unwrap(); - if (ledger_.headTxn() != ledger_.baseTxn()) { - // 候选处于基线态(刚打开的主资源):从基点向头做前向重放——与 - // materializeState(候选在头位,向书签位正/反向走)语义不同。 - auto candidate = cloneWorking(); - WorkspaceErrorInfo replayFailure; - for (const ca::u64 step : ledger_.chainTo(ledger_.headTxn())) { - const auto* row = ledger_.transaction(step); - if (row == nullptr) { - replayFailure = error(WorkspaceError::DataLoss, "bind ledger store", - "chain step " + std::to_string(step) + - " is not on the active chain"); - break; - } - replayFailure = applyLedgerTransaction(*candidate, *row, "bind ledger store"); - if (replayFailure.is_err()) break; - } - if (replayFailure.is_err()) { - // 回落纯内存引擎(持有锁已随 move 释放),保留旧基线 digest。 - auto fallback = project::EditLedgerStore::createInMemory( - project::copyLedgerText(previousDigest)); - if (fallback.is_err()) { - return ca::core::Err(ledgerFailure("bind ledger store", - std::move(fallback).unwrap_err())); - } - ledger_ = std::move(fallback).unwrap(); - return ca::core::Err(std::move(replayFailure)); - } - working_ = std::move(candidate); - } - revision_ = 0; - redoHint_.clear(); - return ca::core::Ok(); -} - -WorkspaceResult WorkspaceBase::flushLedger() -{ - auto flushed = ledger_.flush(project::rfc3339UtcNow()); - if (flushed.is_err()) - return ca::core::Err(ledgerFailure("flush ledger", std::move(flushed).unwrap_err())); - autoCompactLedger(); - return ca::core::Ok(); -} - -WorkspaceResult WorkspaceBase::compactLedger( - const project::LedgerCompactRequest& request) -{ - const WorkspaceErrorInfo loaded = ensureLoaded("compact ledger"); - if (loaded.is_err()) return ca::core::Err(std::move(loaded)); - // 手动压缩前置无 pending(有则先 flush——数据安全序,见 §5.4)。 - if (ledger_.hasPending()) { - auto flushed = ledger_.flush(project::rfc3339UtcNow()); - if (flushed.is_err()) - return ca::core::Err(ledgerFailure("compact ledger", std::move(flushed).unwrap_err())); - } - const auto point = ledger_.retentionPoint(request, false, nullptr); - if (point <= ledger_.baseTxn()) { - return ca::core::Err(error(WorkspaceError::FailedPrecondition, "compact ledger", - "retention point does not advance past anchor")); - } - try { - auto candidate = cloneWorking(); - const auto materialized = materializeState(candidate, point, "compact ledger"); - if (materialized.is_err()) return ca::core::Err(materialized); - std::vector>> entries; - std::vector uris; - candidate->visitResources( - [&](std::string_view uri, const model::IResource&) { uris.push_back(std::string(uri)); }); - std::sort(uris.begin(), uris.end()); - for (const auto& uri : uris) { - const auto* resource = candidate->getResource(uri); - if (resource == nullptr) continue; - auto bytes = bytesOf(*resource); - if (bytes.is_err()) return ca::core::Err(std::move(bytes).unwrap_err()); - entries.emplace_back(project::copyLedgerText(uri), std::move(bytes).unwrap()); - } - auto report = ledger_.compact(request, project::serializeContainerEntries(entries), - project::rfc3339UtcNow(), false); - if (report.is_err()) - return ca::core::Err(ledgerFailure("compact ledger", std::move(report).unwrap_err())); - return ca::core::Ok(std::move(report).unwrap()); - } - catch (const std::exception& exception) { - return ca::core::Err(error(WorkspaceError::Internal, "compact ledger", exception.what())); - } -} - -} // namespace mjbase::workspace diff --git a/mjbase/src/mjbase/workspace/workspace.hpp b/mjbase/src/mjbase/workspace/workspace.hpp deleted file mode 100644 index 4769d28..0000000 --- a/mjbase/src/mjbase/workspace/workspace.hpp +++ /dev/null @@ -1,461 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "mjbase/jar/jar_reader.hpp" -#include "mjbase/model/resource_container.hpp" -#include "mjbase/project/edit_ledger_store.hpp" -#include "mjbase/workspace/i_workspace.hpp" -#include "mjbase/workspace/workspace_types.hpp" - -namespace mjbase { - -class KeyPool; ///< 驻留池(keys.hpp;此处仅前置声明) - -namespace hierarchy { -class ClassHierarchy; ///< 层次图(hierarchy 模块;此处仅前置声明) -} // namespace hierarchy - -namespace workspace { - -/// @file workspace.hpp -/// @brief WorkspaceBase:可回滚的、单线程持有的 class/JAR 分析与编辑工作区(基础实现)。 -/// -/// 底座按 libjbc 定型: -/// - class 解码/检索/搜索走 jbc::core::readClass 骨架 + jbc::ir 会话(按需 promote); -/// - 候选验证接 mjbase::spi 协作面:spi::sanitizeClassFile 引用语义预检(闸门语义 -/// 在候选入口前置)+ mjbase::jasm 验证; -/// - 结构化修改经 ClassMutationService(libjbc 会话改写 + spi::ClassResourcePendingCommit -/// 两阶段写回); -/// - 错误通道为 WorkspaceError + WorkspaceResult(旧 ca::Status/StatusOr 的映射见 -/// workspace_types.hpp); -/// - remap/rename/pipeline/workflow 方法族落在 spi 协作面上:管线组装经 -/// spi::PipelineBuilder/applyTo(pass-contract §9 装配层),重映射经 -/// asm_remapper::applyResourceRemapPlan,帧重算经 -/// spi::CommonSuperClassResolver + StrictTypeResolverAdapter(jdkPath 层次 -/// 图由本类扫描 JDK 归档构建)。 -class WorkspaceBase : public IWorkspace -{ -public: - WorkspaceBase(); - ~WorkspaceBase() override; - - WorkspaceBase(const WorkspaceBase&) = delete; - WorkspaceBase& operator=(const WorkspaceBase&) = delete; - WorkspaceBase(WorkspaceBase&&) = delete; - WorkspaceBase& operator=(WorkspaceBase&&) = delete; - - /// @brief 已注册 pass 摘要(静态查询:无工作区状态,不进接口)。 - static std::vector availablePasses(); - - /// @brief 已注册 pipeline 摘要(静态查询:无工作区状态,不进接口)。 - static std::vector availablePipelines(); - - /// @brief 打开主 JAR 并建立不可变原始快照与可修改工作副本。 - WorkspaceResult openJar(const std::string& path) override; - - /// @brief 打开主 JAR(#1110 族:可注入条目名宽容解码回调;缺省空 = 原始 - /// 字节直通零漂移)。非虚重载:decoder 只贯通本实现的主 JAR 导入面 - /// (importFromJar),接口层 IWorkspace::openJar 维持单形参不动。 - /// @param decoder 条目名宽容解码回调(同 mjbase::jar::IJarReader::setEntryNameDecoder; - /// 与 listJarEntries/资源 URI 消费面保持同一解码口径)。 - WorkspaceResult openJar(const std::string& path, - const mjbase::jar::IJarReader::EntryNameDecoder& decoder); - - /// @brief 打开单个 classfile,并以其 JVM internal name 建立稳定资源 URI。 - WorkspaceResult openClass(const std::string& path) override; - - /// @brief 添加只读 supporting JAR,用于后续层次和调用解析。 - /// @return 返回当前 Workspace 内稳定的 support ID 和内容 fingerprint;重复路径或内容被拒绝。 - WorkspaceResult addSupportingJar(const std::string& path) override; - - /// @brief 带角色注册 supporting 容器:library/runtime 挂载只产生一条 - /// 容器记录(与 support 共表,路径/内容唯一约束跨角色生效);support - /// ID 是该容器全部角色注册的唯一 detach 句柄。 - WorkspaceResult addSupportingContainer( - const std::string& path, model::ContainerRole role) override; - - /// @brief 角色标签化的在册表登记内核(addSupportingJar/Container 共用; - /// 一容器一记录,role 只是记录标签)。 - WorkspaceResult addSupportingRole(const std::string& path, - const std::string& roleLabel); - - /// @brief 按 support ID 移除只读 supporting JAR,并失效依赖 classpath 的分析缓存。 - /// @return 返回被移除的 supporting resource 描述;ID 不存在时返回 NotFound。 - WorkspaceResult removeSupportingJar(const std::string& id) override; - - /// @brief 工作区是否已加载主 class 或 JAR。 - bool isLoaded() const noexcept override; - - /// @brief 当前主制品种类;未加载时返回 ArtifactKind::None。 - ArtifactKind artifactKind() const noexcept override; - - /// @brief 当前主资源的来源路径。 - const std::string& sourcePath() const noexcept override; - - /// @brief 单 class 工作区的稳定资源 URI;其他 kind 返回空字符串。 - const std::string& primaryClassUri() const noexcept override; - - /// @brief 当前工作区 revision;只在已提交变更、undo、redo 或 restore 后递增。 - ca::u64 revision() const noexcept override; - - /// @brief 当前工作副本是否和原始快照不同。 - bool isDirty() const override; - - /// @brief 当前工作副本是否满足导出条件。 - bool canExport() const noexcept override; - - /// @brief 返回主资源的稳定排序摘要。 - WorkspaceResult> listResources(bool includeClasses = true) const override; - - /// @brief 读取一个主资源的原始字节。 - WorkspaceResult readResource(const std::string& uri) const override; - - /// @brief 按资源 URI 或 internal name 读取 class 原始字节和内容 fingerprint。 - /// @note 精确 URI 指向损坏 class 时仍返回 bytes,此时 internalName 为空。 - WorkspaceResult readClassBytes(const std::string& uriOrInternalName) const override; - - /// @brief 返回所有主 class 的轻量结构摘要。 - WorkspaceResult> listClassSummaries() const override; - - /// @brief 返回所有主 class 的完整结构化元数据。 - WorkspaceResult> listClasses() const override; - - /// @brief 通过资源 URI 或 JVM internal name 查询 class 详情。 - WorkspaceResult classInfo(const std::string& uriOrInternalName) const override; - - /// @brief 枚举 class 常量池条目。 - WorkspaceResult> constantPool(const std::string& uriOrInternalName, - bool resolve) const override; - - /// @brief 以 owner/name/descriptor 精确查询方法详情。 - WorkspaceResult methodInfo(const std::string& owner, const std::string& name, - const std::string& descriptor) const override; - - /// @brief 以 owner/name/descriptor 精确查询字段详情。 - WorkspaceResult fieldInfo(const std::string& owner, const std::string& name, - const std::string& descriptor) const override; - - /// @brief 返回精确选择方法的逐指令摘要及行号元数据。 - WorkspaceResult methodBytecode(const std::string& owner, - const std::string& name, - const std::string& descriptor) const override; - - /// @brief 返回精确选择方法的完整指令渲染(保留 CP 索引与字节 PC)。 - WorkspaceResult methodInstructions(const std::string& owner, - const std::string& name, - const std::string& descriptor, - bool resolve) const override; - - /// @brief 返回精确选择方法的规范化 Jasm 声明。 - jasm::Result getMethodJasm( - const std::string& owner, const std::string& name, const std::string& descriptor) const override; - - /// @brief 返回主资源 MANIFEST 的稳定排序主属性。 - WorkspaceResult> manifestAttributes() const override; - - /// @brief 提取主资源所有非 class 条目中的最短长度为 @p minimumLength 的可打印字符串。 - WorkspaceResult> resourceStrings(ca::usize minimumLength = 4) const override; - - /// @brief 按 class 内部名或资源 URI 查询 class;损坏 class 会被跳过。 - WorkspaceResult> searchClasses( - const std::string& query, const SearchOptions& options = {}) const override; - - /// @brief 按 owner/name/descriptor/signature 查询字段和方法;损坏 class 会被跳过。 - WorkspaceResult> searchMembers( - const std::string& query, const SearchOptions& options = {}) const override; - - /// @brief 查询 class 常量池字符串和非 class 可打印资源字符串;损坏 class 会被跳过。 - WorkspaceResult> searchStrings( - const std::string& query, const SearchOptions& options = {}) const override; - - /// @brief 精确查询常量池及即时数值字面量;数值文本支持等价的标准数值写法。 - WorkspaceResult> searchNumbers( - const std::string& query, const SearchOptions& options = {}) const override; - - /// @brief 按 mnemonic、opcode、节点类型和指令操作数查询方法指令;损坏 class 会被跳过。 - WorkspaceResult> searchInstructions( - const std::string& query, const SearchOptions& options = {}) const override; - - /// @brief 精确返回所有编码目标为 owner/name/descriptor 的方法引用。 - WorkspaceResult> xrefsToMethod(const std::string& owner, - const std::string& name, - const std::string& descriptor) const override; - - /// @brief 精确返回所有编码目标为 owner/name/descriptor 的字段引用。 - WorkspaceResult> xrefsToField(const std::string& owner, - const std::string& name, - const std::string& descriptor) const override; - - /// @brief 返回所有直接或结构性引用 internalName 的位置。 - WorkspaceResult> xrefsToClass(const std::string& internalName) const override; - - /// @brief 返回方法的直接调用目标;resolved 为 true 时扩展虚调用和接口调用。 - WorkspaceResult> calleesOfMethod(const std::string& owner, - const std::string& name, - const std::string& descriptor, - bool resolved = false) const override; - - /// @brief 返回目标 class 的超类、接口、直接/全部子类和实现者。 - WorkspaceResult classRelations(const std::string& internalName) const override; - - /// @brief 返回精确方法所在的继承/实现覆盖组件。 - WorkspaceResult> overridesOfMethod( - const std::string& owner, const std::string& name, const std::string& descriptor) const override; - - /// @brief 返回可供 MCP/CLI 直接传输的完整 class JASM。 - jasm::Result getJasm(const std::string& uri) const override; - - /// @brief 在不提交的候选副本中汇编并验证完整 class JASM。 - MutationResult validateJasm(const std::string& uri, ca::str::Utf16StringRef sourceName, - ca::str::Utf16StringRef source, - const MutationRequest& request = {}) override; - - /// @brief 原子应用完整 class JASM,成功后产生一个新的 revision。 - MutationResult applyJasm(const std::string& uri, ca::str::Utf16StringRef sourceName, - ca::str::Utf16StringRef source, - const MutationRequest& request = {}) override; - - /// @brief 验证由专用 classfile adapter 生成的完整 class 候选字节,不提交工作区。 - MutationResult validateClassBytes(const std::string& uri, - const std::vector& classBytes, - const MutationRequest& request = {}) override; - - /// @brief 原子应用已由专用 classfile adapter 生成的完整 class 候选字节。 - MutationResult applyClassBytes(const std::string& uri, - const std::vector& classBytes, - const MutationRequest& request = {}) override; - - /// @brief 在不提交的候选副本中应用并验证一项结构化 class 修改。 - MutationResult validateClassMutation(const ClassMutation& mutation, - const MutationRequest& request = {}) override; - - /// @brief 原子应用一项结构化 class 修改,成功后产生一个新的 revision。 - MutationResult applyClassMutation(const ClassMutation& mutation, - const MutationRequest& request = {}) override; - - /// @brief 原子替换一个 class 的原始字节;dry-run 时只返回完整验证和 diff。 - /// - /// 选择器语义(URI 或内名):内名命中 Multi-Release 族时按 resolveClassUri - /// 的确定序选定唯一条目。**单条 URI 语义**:替换只作用于选定条目,同族 - /// 其它条目(versions/<N> 变体)字节不动、不传播——族员字节本就随 - /// release 各自独立,逐条 URI 调用即完整族语义;本层不提供族批量替换面。 - /// 候选字节内名必须与目标 URI 推导内名一致(版本化目标按 JEP 238 ≥ 9 口径)。 - MutationResult replaceClassBytes(const std::string& uriOrInternalName, - const std::vector& bytes, - const MutationRequest& request = {}) override; - - /// @brief 返回当前工作副本中相对打开时原始快照已变化的 class。 - WorkspaceResult> modifiedClasses() const override; - - /// @brief 在不提交候选副本的情况下分析一个显式 MemberRemapPlan 的影响。 - ImpactAnalysis analyzeRemap(const asm_remapper::MemberRemapPlan& plan, - const asm_remapper::RemapPolicy& policy) const override; - - /// @brief 原子应用一个显式 MemberRemapPlan。 - MutationResult applyRemap(const asm_remapper::MemberRemapPlan& plan, - const asm_remapper::RemapPolicy& policy, - const MutationRequest& request = {}) override; - - /// @brief 生成重命名映射和风险报告,不修改工作副本。 - RenameAnalysis analyzeRename(const RenameRequest& request) const override; - - /// @brief 原子应用一个已通过 override 风险检查的重命名请求。 - MutationResult applyRename(const RenameRequest& request, - const MutationRequest& requestOptions = {}) override; - - /// @brief 在不提交候选副本的情况下运行 pass/pipeline 并返回验证和 byte diff。 - MutationResult validatePipeline(const PipelineRequest& pipeline, - const MutationRequest& request = {}) override; - - /// @brief 原子执行 pass/pipeline;dryRun 时与 validatePipeline 返回同一类结果。 - MutationResult applyPipeline(const PipelineRequest& pipeline, - const MutationRequest& request = {}) override; - - /// @brief 在一个候选事务中执行 remap、可选 pipeline、验证和候选 xref。 - WorkflowResult runWorkflow(const WorkflowRequest& workflow, - const MutationRequest& request = {}) override; - - /// @brief 导入一个私有工作流候选 JAR,并按当前 revision 验证、导出和提交。 - /// @warning candidateJarPath 仅限受控后台事务生成的 JAR;提交失败会移除本次创建的 exportPath。 - WorkflowCandidateCommitResult commitWorkflowCandidateJar( - const WorkflowCandidateCommitRequest& candidate, - const MutationRequest& request = {}) override; - - /// @brief 比较当前工作副本与一个外部 JAR 的全部可读取资源,不修改工作区。 - JarDiffResult diffJar(const std::string& referencePath) const override; - - /// @brief 扫描并预览当前工作副本的 JAR 修复候选,不提交变更。 - JarRepairResult analyzeJarRepair(const JarRepairRequest& repair, - const MutationRequest& request = {}) const override; - - /// @brief 原子应用已显式选择的 JAR 修复;dryRun 时返回预览结果。 - JarRepairResult applyJarRepair(const JarRepairRequest& repair, - const MutationRequest& request = {}) override; - - /// @brief 将指定 class 恢复到打开工作区时的原始字节。 - MutationResult revertClass(const std::string& uri, const MutationRequest& request = {}) override; - - /// @brief 保存当前工作副本的具名快照,不改变 revision。 - /// - /// 台账换底后快照 = 台账书签(命名指针指向当前头事务),零字节代价; - /// flush 后以书签行持久(跨 flush 存活是相对旧克隆实现的已声明语义变化, - /// 见 doc_ai/analysis-project-schema-v2.md §4)。 - WorkspaceResult createSavepoint(const std::string& name) override; - - /// @brief 恢复具名快照,产生新 revision。 - MutationResult restoreSavepoint(const std::string& name, - const MutationRequest& request = {}) override; - - /// @brief 撤销上一次已提交的事务。 - MutationResult undo(const MutationRequest& request = {}) override; - - /// @brief 重做上一次撤销的事务。 - MutationResult redo(const MutationRequest& request = {}) override; - - /// @brief 绑定工程 bundle 的台账持久层(可选——不绑定则台账 - /// 纯内存,undo/redo/savepoint 行为不变、随对象销毁蒸发)。 - /// - /// 打开/复用 bundle 的活动台账代:装载在册事务链与书签;链非空时工作副本 - /// 物化为「基线 ⊕ 重放」(基线 = 压缩基线 section 或本会话原始快照)。持有 - /// bundle 单写者锁至对象销毁;revision 从 0 重计(会话内乐观控制语义不变, - /// 台账事务号独立单调)。失败 = LedgerOpen 族错误(WorkspaceErrorInfo)。 - WorkspaceResult bindLedgerStore(const std::string& bundleDir); - - /// @brief 把 pending overlay 与台账事务落盘到绑定 bundle(无绑定 = no-op)。 - /// - /// close/export 前的挂点:exportJar/exportClass 内部已前置调用。崩溃安全 - /// 与代际协议见 doc_ai/analysis-project-schema-v2.md §3。 - WorkspaceResult flushLedger(); - - /// @brief 手动台账压缩(RFC §6.4;自动阈值触发走 flush/savepoint 时机)。 - /// - /// 保留参数语义见 LedgerCompactRequest;结果列明保留点、被显式丢弃书签与 - /// 释放的 section。压缩前需无 pending(先 flushLedger)。 - WorkspaceResult compactLedger( - const project::LedgerCompactRequest& request); - - /// @brief 将当前工作副本原子写入一个此前不存在的输出 JAR。 - WorkspaceResult exportJar(const std::string& outputPath) const override; - - /// @brief 将单 class 工作副本原子写入一个此前不存在的输出 classfile。 - WorkspaceResult exportClass(const std::string& outputPath) const override; - - /// @brief 返回已附加的 supporting JAR 摘要。 - std::vector supportingResources() const override; - -private: - struct SupportingResource; - struct AnalysisGraph; - - WorkspaceErrorInfo ensureLoaded(const std::string& operation) const; - bool accepts(const MutationRequest& request) const; - MutationResult revisionConflict() const; - MutationResult stateFailure(WorkspaceErrorInfo status) const; - MutationResult commit(std::unique_ptr candidate, - std::vector diagnostics, - const MutationRequest& request); - /// @brief 台账错误 → workspace 错误面(kind 映射 + operation 前缀)。 - WorkspaceErrorInfo ledgerFailure(const std::string& operation, - const project::ProjectStoreError& cause) const; - /// @brief 容器 canonical digest(SHA-256 口径,schema v2 裁决;§1.6)。 - WorkspaceResult ledgerBaseDigest() const; - /// @brief 把台账事务正向应用到容器(redo/restore/重放共用)。 - WorkspaceErrorInfo applyLedgerTransaction(model::ResourceContainer& container, - const project::LedgerTransaction& txn, - const std::string& operation) const; - /// @brief 反向应用一条事务(undo 的逆操作:before 字节恢复/新增撤销/删除还原)。 - WorkspaceErrorInfo inverseApplyLedgerTransaction(model::ResourceContainer& container, - const project::LedgerTransaction& txn, - const std::string& operation) const; - /// @brief before 侧字节解析:上一次 touch 的 overlay 载荷,缺 touch 则回落 - /// 基线(压缩基线 section 或原始快照);digest 不符 = 数据损坏错误。 - WorkspaceResult> resolveBeforeBytes( - const project::LedgerTransaction& txn, const project::LedgerEntry& entry) const; - /// @brief 头位置物化为候选容器:forward 应用 (head, txn],reverse 应用 - /// (txn, head]。 - WorkspaceErrorInfo materializeState(std::unique_ptr& candidate, - ca::u64 txn, const std::string& operation) const; - /// @brief 台账自动压缩挂点(flush/savepoint 时机;阈值未到/未绑定即跳过)。 - void autoCompactLedger(); - /// @brief 按 uri 形态选择资源类型重建条目(.class → Class、MANIFEST.MF → - /// Manifest、其余 Unknown)。 - static std::unique_ptr resourceFromBytes(const std::string& uri, - std::vector bytes); - /// @brief 重放基线容器:有压缩基线 section 读之,否则克隆原始快照。 - WorkspaceResult> replayBaseline() const; - - std::unique_ptr cloneWorking() const; - std::unique_ptr cloneOriginal() const; - /// @brief 工作副本 + supporting JAR 的合并只读容器(xref/callees 扫描面; - /// 主容器 URI 优先,同 URI 的支撑资源跳过)。 - std::unique_ptr analysisScanContainer() const; - WorkspaceResult analysisGraph() const; - /// @brief 内名/URI 选择器 → 唯一 class 资源 URI(Multi-Release 族确定序)。 - /// - /// 选择器为既有 class URI 时原样返回;否则按 class 字节内名匹配。同内名 - /// 族(base 与 META-INF/versions/<N> 变体并存)按确定序选定唯一条目: - /// base 形条目优先于版本化条目(JEP 238 口径版本 ≥ 9),同层级取 URI 字节 - /// 序最小——不依赖容器迭代序。 - WorkspaceResult resolveClassUri(const model::ResourceContainer& container, - const std::string& uriOrInternalName) const; - /// @brief 编辑校验门:只校验 @p scopeUris 列出的类(门禁作用域 = - /// 操作受影响面;容器基线未编辑类不参与门禁——它们编辑前就存在)。 - /// - /// mode 三档:relaxed(默认,JVM 赋值兼容语义)/ strict(原精确 - /// 匹配)/ off(直通零校验)。同类多条诊断聚合为 per-class 条目(类名 + - /// 计数 + 截断明细),不再逐方法刷屏。层次图按候选容器即时重建 - /// (外部/JDK 类型不可解析不判非法)。 - WorkspaceErrorInfo validateCandidate(const model::ResourceContainer& candidate, - std::vector& diagnostics, - jasm::ValidationMode mode, - const std::vector& scopeUris) const; - /// @brief 在候选副本上组装并运行 pass/pipeline(runWorkflow 前置协议见 - /// i_workspace.hpp:pipeline/passNames 互斥,均空取 default-optimize)。 - WorkspaceErrorInfo runPipeline(model::ResourceContainer& candidate, - const PipelineRequest& request) const; - /// @brief 把重命名请求展开为重映射计划 + 审阅映射表 + 风险清单 - /// (analyzeRename/applyRename 共用的展开面)。 - WorkspaceErrorInfo buildRenamePlan(const RenameRequest& request, - asm_remapper::MemberRemapPlan& plan, - std::vector& mappings, - std::vector& overrides, - std::vector& risks) const; - /// @brief 引用结果来源装饰:sourceClass 落在 supporting JAR 时填 supporting/supportId。 - void annotateSupportingSources(std::vector& references) const; - std::unordered_map supportingClassIds() const; - - model::ResourceContainer* workingContainer() noexcept; - const model::ResourceContainer* workingContainer() const noexcept; - - std::unique_ptr original_; - std::unique_ptr working_; - std::vector supporting_; - /// 台账引擎:undo/redo = 头指针回退/前进,savepoint = 书签; - /// 未绑定为纯内存台账(行为与旧克隆实现一致,随对象销毁蒸发)。mutable: - /// 导出路径(const)前置 flush 挂点。 - mutable project::EditLedgerStore ledger_; - /// redo 目标栈(undo 压入、redo 弹出、commit 清空——对齐旧 redo_ 语义)。 - std::vector redoHint_; - ArtifactKind artifactKind_ = ArtifactKind::None; - std::string sourcePath_; - std::string primaryClassUri_; - ca::u64 revision_ = 0; - ca::u64 nextSupportingId_ = 1; -}; - -/// @brief 递归扫描 JDK 目录下的 .jar/.zip/.jmod,把层次摘要灌入 @p hierarchy。 -/// -/// CommonSuperClassProvider::scan 的对应物(pipeline jdkPath 语义): -/// 单归档导入失败 fail-open 跳过;单类 fail-open(环检查拒绝等);jimage -/// 模块镜像不扫描。配合 spi::CommonSuperClassResolver + -/// StrictTypeResolverAdapter 注入 PassPipelineOptions::typeResolver 使用; -/// 调用方保证 pool/hierarchy 存活覆盖管线 build+run 全程。 -void scanJdkHierarchy(const std::string& jdkPath, mjbase::hierarchy::ClassHierarchy& hierarchy, - mjbase::KeyPool& pool); - -} // namespace workspace -} // namespace mjbase diff --git a/mjbase/src/mjbase/workspace/workspace_types.hpp b/mjbase/src/mjbase/workspace/workspace_types.hpp deleted file mode 100644 index 2b22f9b..0000000 --- a/mjbase/src/mjbase/workspace/workspace_types.hpp +++ /dev/null @@ -1,693 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "libca/core/datatype.hpp" -#include "libca/core/result.hpp" - -#include "mjbase/asm/remapper/member_remap_plan.hpp" -#include "mjbase/asm/remapper/member_remapper.hpp" -#include "mjbase/jasm/jvm_jasm_service.hpp" - -namespace mjbase::workspace { - -/// @file workspace_types.hpp -/// @brief Workspace 接口层共享的数据传输类型(DTO)。 -/// -/// 无业务语义的纯数据结构 + 枚举 + 常量:IWorkspace 接口签名与 WorkspaceBase -/// 实现共同使用。类型面按 -/// mjbase 约定重定型: -/// - 错误通道:ca::Status/StatusOr → WorkspaceError + WorkspaceResult -/// (mjbase 错误走 Result 值语义的总约定);DTO 内嵌的 ca::Status 字段 -/// 同步改 WorkspaceErrorInfo; -/// - 诊断:jasm::Diagnostic 即 mjbase::jasm::Diagnostic; -/// - 重映射契约类型:RemapPolicy/MemberRemapPlan 取 mjbase::asm_remapper 域 -/// (KeyPool 驻留 + MemberKey 建键版)。 -/// RenameRequest 族、PipelineRequest 族、WorkflowRequest 族随工作区 remap/ -/// rename/pipeline 事务面一起迁入(mjt-mcp 为第二个管线消费方,触发装配层 -/// 定型,见 pass-contract §9)。 - -/// @brief workspace 模块错误码。与旧 ca::StatusCode 用点的映射: -/// InvalidArgument/NotFound/AlreadyExists/FailedPrecondition/ -/// PermissionDenied/Aborted/DataLoss/Internal/Unimplemented 一一对应。 -enum class WorkspaceError : ca::u8 -{ - Ok = 0, - InvalidArgument, - NotFound, - AlreadyExists, - FailedPrecondition, - PermissionDenied, - Aborted, - DataLoss, - Internal, - Unimplemented, -}; - -/// @brief 错误码 → 短名(断言输出/日志用)。 -const char* to_string(WorkspaceError error) noexcept; - -/// @brief workspace 模块错误值:码 + 消息文本(结构对齐 spi::PipelineError 的 -/// 模块边界错误形态——Result 错误通道要求可拷贝,诊断文本落在 std::string)。 -/// message 沿用旧 ca::Status 的 "operation: detail" 拼接口径。 -struct WorkspaceErrorInfo -{ - WorkspaceError code = WorkspaceError::Ok; - std::string message; - - bool is_ok() const noexcept { return code == WorkspaceError::Ok; } - bool is_err() const noexcept { return code != WorkspaceError::Ok; } -}; - -/// @brief workspace 模块 Result 别名;Ok 恒为 code == Ok 的错误值。 -template -using WorkspaceResult = ca::core::Result; - -/// @brief 工作区当前承载的主制品种类。 -enum class ArtifactKind : ca::u8 -{ - None, - Class, - Jar, -}; - -/// @brief 未提供 expectedRevision 时使用的显式哨兵值。 -constexpr ca::u64 kAnyRevision = static_cast(-1); - -/// @brief 单次原始 class bytes 替换允许的服务端硬上限。 -constexpr ca::usize kMaximumClassReplacementSize = 16U * 1024U * 1024U; - -// ── JVM access flags(JVMS 表 4.1/4.4/4.5/4.6;数值即位掩码)── -inline constexpr ca::u32 kAccPublic = 0x0001; -inline constexpr ca::u32 kAccPrivate = 0x0002; -inline constexpr ca::u32 kAccProtected = 0x0004; -inline constexpr ca::u32 kAccStatic = 0x0008; -inline constexpr ca::u32 kAccFinal = 0x0010; -inline constexpr ca::u32 kAccSuper = 0x0020; -inline constexpr ca::u32 kAccSynchronized = 0x0020; -inline constexpr ca::u32 kAccVolatile = 0x0040; -inline constexpr ca::u32 kAccBridge = 0x0040; -inline constexpr ca::u32 kAccTransient = 0x0080; -inline constexpr ca::u32 kAccVarargs = 0x0080; -inline constexpr ca::u32 kAccNative = 0x0100; -inline constexpr ca::u32 kAccInterface = 0x0200; -inline constexpr ca::u32 kAccAbstract = 0x0400; -inline constexpr ca::u32 kAccStrict = 0x0800; -inline constexpr ca::u32 kAccSynthetic = 0x1000; -inline constexpr ca::u32 kAccAnnotation = 0x2000; -inline constexpr ca::u32 kAccEnum = 0x4000; -inline constexpr ca::u32 kAccModule = 0x8000; -inline constexpr ca::u32 kAccRecord = 0x10000; -inline constexpr ca::u32 kAccSealed = 0x10000; -inline constexpr ca::u32 kAccDeprecated = 0x20000; - -/// @brief 读写操作使用的乐观并发控制选项。 -struct MutationRequest final -{ - ca::u64 expectedRevision = kAnyRevision; - bool dryRun = false; - std::string label; - /// @brief 编辑校验门模式:默认 relaxed(JVM 赋值兼容语义);上层 - /// 可透传 strict(原精确匹配)或 off(跳过门禁)。无参调用点零改动。 - jasm::ValidationMode validationMode = jasm::ValidationMode::Relaxed; -}; - -/// @brief 一个变更字节区间,长度分别描述变更前后内容。 -struct ByteRange final -{ - ca::usize offset = 0; - ca::usize beforeLength = 0; - ca::usize afterLength = 0; -}; - -/// @brief 单个资源的紧凑字节级 diff 摘要。 -struct ByteDiff final -{ - std::string uri; - ca::usize beforeSize = 0; - ca::usize afterSize = 0; - std::vector ranges; -}; - -/// @brief 修改 class 后重新索引到的直接引用事实。 -struct ReferenceImpact final -{ - std::string kind; - std::string target; -}; - -/// @brief 所有修改操作共用的稳定结果协议。 -/// -/// status 为 Ok 时 committed/exportable/revision 才有意义;失败路径仍回填 -/// revision 与 exportable(与旧 ca::Status 语义一致)。 -struct MutationResult final -{ - WorkspaceErrorInfo status; - ca::u64 revision = 0; - bool committed = false; - bool exportable = false; - std::vector diagnostics; - std::vector byteDiffs; - std::vector affectedClasses; - std::vector affectedReferences; - - bool is_ok() const noexcept { return status.is_ok(); } -}; - -/// @brief JAR 内资源的稳定摘要。 -struct ResourceSummary final -{ - std::string uri; - std::string type; - ca::usize size = 0; -}; - -/// @brief 资源的原始字节读取结果。 -struct ResourceContent final -{ - ResourceSummary summary; - std::vector bytes; -}; - -/// @brief 一个 class 资源的原始字节和稳定内容标识。 -/// -/// internalName 在 class header 损坏时为空;调用方仍可通过精确资源 URI 读取原始字节用于诊断。 -struct ClassBytesInfo final -{ - std::string resourceUri; - std::string internalName; - std::string fingerprint; - std::vector bytes; -}; - -/// @brief 当前工作副本中一个相对原始 JAR 已变化的 class 摘要。 -struct ModifiedClassInfo final -{ - std::string resourceUri; - std::string originalInternalName; - std::string currentInternalName; - std::string originalFingerprint; - std::string currentFingerprint; - ca::usize originalSize = 0; - ca::usize currentSize = 0; -}; - -/// @brief 注解元素的完整值树。 -struct AnnotationElement final -{ - std::string name; - char tag = 's'; - std::string value; - std::string annotationDescriptor; - std::vector children; -}; - -/// @brief 一个运行时可见或不可见注解。 -struct AnnotationInfo final -{ - std::string descriptor; - bool visible = false; - std::vector elements; -}; - -/// @brief 局部变量调试表条目。 -struct LocalVariableInfo final -{ - std::string name; - std::string descriptor; - std::string signature; - ca::i32 index = 0; -}; - -/// @brief 方法字节码中的源码行号映射。 -/// -/// instructionIndex 为方法体内代码指令序号(不含 label/line 节点——mjbase -/// 底座 CodeDom 中标签是句柄不是指令);pc 无对应指令时为 -1。 -struct LineNumberInfo final -{ - ca::i32 instructionIndex = -1; - ca::i32 line = -1; -}; - -/// @brief 一条方法字节码指令的稳定摘要。 -struct InstructionInfo final -{ - ca::i32 index = -1; - ca::i32 opcode = -1; - std::string mnemonic; - std::string nodeType; -}; - -/// @brief switch 指令的一个跳转分支(key 与绝对目标 PC)。 -struct SwitchCaseInfo final -{ - ca::i32 key = 0; - ca::i32 targetPc = 0; -}; - -/// @brief 一条字节码指令的完整渲染信息(保留 CP 索引与字节 PC)。 -/// @details 供 info/disasm 消费。pc 为该指令在方法 code 数组中的字节偏移; -/// cpIndex 为该指令引用的常量池条目索引(无引用为 -1);operandText 为助记符后 -/// 的操作数文本(如 "#5"/"3, 1"/"#5 dims=2");operandComment 为 resolve 后的 -/// 人类可读注释(owner.name:desc / "str" / 字面值,已过 IdentifierUtil 可读化); -/// switchDefaultPc>=0 时该指令是 switch,switchCases 列出各分支。 -struct InstructionDetailInfo final -{ - ca::i32 pc = -1; - ca::i32 opcode = -1; - std::string mnemonic; - ca::i32 cpIndex = -1; - std::string operandText; - std::string operandComment; - bool isSwitch = false; - ca::i32 switchDefaultPc = -1; - std::vector switchCases; -}; - -/// @brief 一个方法的完整指令渲染。 -struct MethodInstructions final -{ - ca::i32 maxStack = 0; - ca::i32 maxLocals = 0; - std::vector instructions; -}; - -/// @brief 常量池条目的稳定摘要。 -/// @details tag 为标签名("Utf8"/"Integer"/"MethodRef" 等,与 JVM 规范常量名对齐); -/// text 为条目值文本:非引用类条目总是填充;引用类条目仅在 resolve=true 时填充 -/// 解析后的 owner.name:desc 文本,否则为空。malformed 标记 Long/Double 的第二槽 -/// 占位条目(tag/text 为空,保留索引占位使条目数与常量池槽数一致)。 -struct ConstantPoolEntryInfo final -{ - ca::usize index = 0; - std::string tag; - std::string text; - bool malformed = false; -}; - -/// @brief 常量值摘要。text 为中性渲染文本(无类型后缀、字符串不加引号)。 -struct ConstantValueInfo final -{ - /// @brief 常量值类型,决定 text 的量纲与调用方应补的后缀。 - enum class Kind - { - Integer, - Long, - Float, - Double, - String - }; - Kind kind = Kind::Integer; - std::string text; -}; - -/// @brief 字段的结构化元数据。 -struct FieldInfo final -{ - std::string name; - std::string descriptor; - std::string signature; - ca::i32 access = 0; - std::vector annotations; - /// ConstantValue 属性;无该属性则为 nullopt。 - std::optional constantValue; -}; - -/// @brief 方法的结构化元数据。 -struct MethodInfo final -{ - std::string name; - std::string descriptor; - std::string signature; - ca::i32 access = 0; - ca::i32 maxStack = 0; - ca::i32 maxLocals = 0; - ca::usize instructionCount = 0; - ca::usize tryCatchCount = 0; - std::vector exceptions; - std::vector localVariables; - std::vector lineNumbers; - std::vector annotations; -}; - -/// @brief 精确选择的方法及其字节码指令摘要。 -struct MethodBytecode final -{ - MethodInfo method; - std::vector instructions; -}; - -/// @brief class 列表使用的轻量结构摘要。 -/// @details 不保留 signature、annotation、成员详情或方法体;详细查询使用 ClassInfo。 -struct ClassSummary final -{ - std::string resourceUri; - std::string internalName; - std::string superName; - ca::i32 access = 0; - ca::i32 version = 0; - ca::usize interfaceCount = 0; - ca::usize fieldCount = 0; - ca::usize methodCount = 0; -}; - -/// @brief class 的结构化元数据。 -struct ClassInfo final -{ - std::string resourceUri; - std::string internalName; - std::string superName; - std::string signature; - ca::i32 access = 0; - ca::i32 version = 0; - std::vector interfaces; - std::vector fields; - std::vector methods; - std::vector innerClasses; - std::vector annotations; -}; - -/// @brief MANIFEST 主属性的稳定键值条目。 -struct ManifestAttribute final -{ - std::string key; - std::string value; -}; - -/// @brief 非 class 资源中提取出的可打印字符串。 -struct ResourceStringInfo final -{ - std::string uri; - ca::usize offset = 0; - std::string value; -}; - -/// @brief 一个可分页传输的结构化搜索命中。 -/// -/// kind 表示命中来源,例如 class、field、method、constant_pool_string、resource_string、 -/// constant_pool_number、instruction 或 instruction_number。instructionIndex 只在方法指令 -/// 命中时有效(代码指令序号);其余命中为 -1。 -struct SearchHit final -{ - std::string resourceUri; - std::string kind; - std::string owner; - std::string name; - std::string descriptor; - ca::i32 instructionIndex = -1; - std::string value; -}; - -/// @brief 文本搜索的匹配模式。 -enum class SearchMatchMode : ca::u8 -{ - Contains, - Exact, - Regex, -}; - -/// @brief Workspace 五类搜索共用的过滤条件。 -/// -/// owner 和 packagePrefix 使用 JVM internal name;memberKind 为空或 field/method;opcode 为 -1 时 -/// 不过滤。并非每类搜索都使用全部字段,例如 class 搜索忽略 memberKind/opcode。 -struct SearchOptions final -{ - SearchMatchMode matchMode = SearchMatchMode::Contains; - bool caseSensitive = false; - std::string owner; - std::string packagePrefix; - std::string memberKind; - ca::i32 opcode = -1; -}; - -/// @brief 方法覆盖组件中的一个声明。 -struct MethodOverrideInfo final -{ - std::string owner; - std::string name; - std::string descriptor; - bool primary = false; - bool supporting = false; - std::string supportId; -}; - -/// @brief 支持库的只读描述。 -struct SupportingResourceInfo final -{ - std::string id; - std::string path; - std::string fingerprint; - ca::usize resourceCount = 0; - ca::usize classCount = 0; - /// @brief 注册角色标签:"support"(缺省遗留值)/"library"/"runtime" - /// (model::to_string(ContainerRole) 口径)。同一容器只产生一条记录, - /// 改变的是本标签而不是追加新条目。 - std::string role = "support"; -}; - -/// @brief 精确 xref 查询返回的引用位置和目标。 -/// -/// instructionIndex 为方法体内代码指令序号;class 级引用(头/结构元数据)为 -1。 -struct ReferenceInfo final -{ - std::string sourceClass; - std::string sourceMethod; - std::string sourceDescriptor; - ca::i32 instructionIndex = -1; - std::string target; - std::string kind; - bool supporting = false; - std::string supportId; -}; - -/// @brief 继承关系结果中一个 class 的来源。 -struct ClassRelationSource final -{ - std::string internalName; - bool supporting = false; - std::string supportId; -}; - -/// @brief 一个 class 的继承、接口和后代闭包。 -struct ClassRelations final -{ - std::vector superclasses; - std::vector interfaces; - std::vector directSubclasses; - std::vector allSubclasses; - std::vector implementors; - std::vector sources; -}; - -/// @brief 影响分析中的一项预期或已应用映射。 -struct RenameMapping final -{ - std::string kind; - std::string before; - std::string after; -}; - -/// @brief 工作区重命名操作的目标种类。 -enum class RenameKind : ca::u8 -{ - Class, - Package, - Field, - Method, - Local, -}; - -/// @brief 一个 class/package/member/local 重命名请求。 -struct RenameRequest final -{ - RenameKind kind = RenameKind::Class; - std::string owner; - std::string methodName; - std::string name; - std::string descriptor; - std::string newName; - ca::i32 localIndex = -1; - bool cascadeOverrides = false; - asm_remapper::RemapPolicy policy = asm_remapper::RemapPolicy::all(); -}; - -/// @brief 对候选映射变更的前后影响和验证结果。 -struct ImpactAnalysis final -{ - WorkspaceErrorInfo status; - bool exportable = false; - std::vector beforeReferences; - std::vector afterReferences; - std::vector byteDiffs; - std::vector diagnostics; - std::vector risks; -}; - -/// @brief 重命名预览,包括方法 override 组件和可供审阅的映射表。 -struct RenameAnalysis final -{ - WorkspaceErrorInfo status; - bool readyToApply = false; - bool requiresOverrideReview = false; - std::vector mappings; - std::vector overrideComponent; - ImpactAnalysis impact; -}; - -/// @brief 一个可由工作区执行的已注册 pass 摘要。 -struct PassDescription final -{ - std::string name; - std::string description; - std::string scope; -}; - -/// @brief 一个可由工作区执行的已注册 pipeline 摘要。 -struct PipelineDescription final -{ - std::string name; - std::string description; -}; - -/// @brief 在工作区候选副本上执行 pass 或 pipeline 的请求。 -/// -/// passNames 与 pipeline 互斥;两者均为空时使用 `default-optimize`。jdkPath 非空时会 -/// 扫描 JDK 类路径并以 COMPUTE_MAXS | COMPUTE_FRAMES 写回候选 class。 -struct PipelineRequest final -{ - std::vector passNames; - std::string pipeline; - std::string jdkPath; -}; - -/// @brief 工作流中要在候选副本前后执行的精确引用查询种类。 -enum class WorkflowReferenceKind : ca::u8 -{ - Class, - Field, - Method, -}; - -/// @brief 一个可重放工作流的精确 xref 查询。 -struct WorkflowReferenceQuery final -{ - WorkflowReferenceKind kind = WorkflowReferenceKind::Class; - std::string owner; - std::string name; - std::string descriptor; -}; - -/// @brief 一个 xref 查询在工作流输入和候选输出中的稳定结果。 -struct WorkflowReferenceReport final -{ - WorkflowReferenceQuery query; - std::vector before; - std::vector after; -}; - -/// @brief 工作流中一个已执行阶段的状态。 -struct WorkflowStageResult final -{ - std::string name; - WorkspaceErrorInfo status; -}; - -/// @brief 一次工作区候选事务的组合请求。 -/// -/// remapPlan 为调用期间借用的已解析计划,必须在 runWorkflow 返回前保持有效。若设置 -/// runPipeline,mapping 和 pipeline 都在同一候选副本中运行;任一阶段失败都不会提交工作区。 -struct WorkflowRequest final -{ - const asm_remapper::MemberRemapPlan* remapPlan = nullptr; - asm_remapper::RemapPolicy remapPolicy = asm_remapper::RemapPolicy::bytecodeOnly(); - bool runPipeline = false; - PipelineRequest pipeline; - std::vector referenceQueries; - /// @brief 非空时将已验证的候选副本原子导出到此前不存在的 JAR 路径。 - /// @note dryRun 只校验导出目标,不创建输出文件。 - std::string exportPath; - /// @brief 在验证完成后接收一次性候选 JAR 快照的只读阶段。 - /// @note 回调返回前必须完成读取,快照会立刻删除;返回错误时不导出或提交 Workspace。 - std::function candidateJarConsumer; - std::string candidateJarStageName; -}; - -/// @brief 可重放工作流的机器可读报告。 -/// -/// inputFingerprint 和 candidateFingerprint 为按资源 URI 和字节内容计算的稳定 FNV-1a-64 摘要。 -/// mutation 复用所有 Workspace 编辑的 revision、diagnostics 和 byte diff 契约;dryRun 时仅 -/// 返回候选报告,成功提交时最多增加一个 revision。 -struct WorkflowResult final -{ - MutationResult mutation; - std::string inputFingerprint; - std::string candidateFingerprint; - std::string exportPath; - bool exported = false; - std::vector stages; - std::vector references; -}; - -/// @brief 一项可由 JAR 修复事务处理的资源问题。 -struct JarRepairIssue final -{ - std::string uri; - std::string kind; - std::string message; - ca::usize size = 0; - bool selectedForRemoval = false; -}; - -/// @brief JAR 修复的显式、可审阅删除策略。 -struct JarRepairRequest final -{ - bool removeInvalidClassEntries = false; - bool removeCorruptOutputMarkers = false; -}; - -/// @brief JAR 修复扫描结果及候选事务预览或提交结果。 -struct JarRepairResult final -{ - MutationResult mutation; - ca::usize resourceCount = 0; - ca::usize classCount = 0; - std::vector issues; -}; - -/// @brief 一个工作区与外部 JAR 的全资源字节比较结果。 -struct JarDiffResult final -{ - WorkspaceErrorInfo status; - std::string referencePath; - ca::usize workspaceOnly = 0; - ca::usize referenceOnly = 0; - ca::usize modified = 0; - std::vector byteDiffs; -}; - -/// @brief 已完成工作流候选 JAR 的内部提交请求。 -/// -/// candidateJarPath 必须由上层事务私有持有,不能直接暴露给不可信客户端。exportPath 非空时会在 -/// commit 前从同一候选原子导出;commit 失败会删除该导出。dryRun 只验证候选和导出目标。 -/// -/// WorkflowRequest(remapPlan/pipeline/referenceQueries 组合事务)依赖未迁的 -/// remapper/pass-registry 设施,不随迁(见 i_workspace.hpp 范围说明);候选 JAR 的 -/// 导入-验证-导出-提交契约由本类型与 commitWorkflowCandidateJar 承载。 -struct WorkflowCandidateCommitRequest final -{ - std::string candidateJarPath; - std::string exportPath; -}; - -/// @brief 工作流候选 JAR 导入、导出和提交的统一结果。 -struct WorkflowCandidateCommitResult final -{ - MutationResult mutation; - std::string exportPath; - bool exported = false; -}; - -} // namespace mjbase::workspace diff --git a/mjbase/test/unittest/workspace/class_mutation_access_test.cpp b/mjbase/test/unittest/workspace/class_mutation_access_test.cpp deleted file mode 100644 index db88ac4..0000000 --- a/mjbase/test/unittest/workspace/class_mutation_access_test.cpp +++ /dev/null @@ -1,225 +0,0 @@ -// 访问位编辑语义(P19)验收面: -// - change_access(新 op):{set, clear} 两组名单增量编辑——未提及位一律保留 -// (ACC_SUPER 等 set_class_access 整体替换下丢失的 flag 不再丢); -// - "deprecated" 是 Deprecated 属性不是 access flag:映射为属性增删,禁止 -// 静默丢弃;旧 op(set_class_access)带 0x20000 位同样映射属性(历史版本 -// 静默丢弃的修补,整体替换语义本身不变——旧工程重放兼容契约); -// - 未知名 flag 报错并列出 class 目标合法 flag 全集;set/clear 相交报错。 -#include -#include - -#include -#include -#include -#include -#include - -#include "jbc/ir/units.hpp" -#include "mjbase/jasm/jvm_jasm_service.hpp" -#include "mjbase/model/resource_container.hpp" -#include "mjbase/model/resource_helper.hpp" -#include "mjbase/model/resource_model.hpp" -#include "mjbase/workspace/classfile_support.hpp" -#include "mjbase/workspace/workspace.hpp" - -namespace mjbase::workspace { - -namespace { - -ca::str::Utf16String utf16(const std::string& value) -{ - return ca::str::Utf16String::from_utf8_string(ca::str::Utf8StringRef::from_data( - reinterpret_cast(value.data()), value.size())); -} - -std::string subjectSource() -{ - return ".super java/lang/Object\n" - ".class public super workspace/Subject {\n" - " .method public static value ()I {\n" - " code: {\n" - " bipush 1\n" - " ireturn\n" - " }\n" - " }\n" - "}\n"; -} - -std::filesystem::path writeAccessFixture(const std::string& fileName) -{ - jasm::JvmJasmService service; - auto result = service.assemble(utf16("Subject.jasm").ref(), - utf16(subjectSource()).ref()); - if (!result.isOk()) throw std::runtime_error("access fixture did not assemble"); - model::ResourceContainer container; - const std::string uri = "workspace/Subject.class"; - container.addResource(uri, std::make_unique( - uri, result.value->bytes)); - const auto path = test::getTempOutPath(fileName); - model::ResourceHelper::exportToJar(container, path.string()); - return path; -} - -MutationRequest request(ca::u64 revision) -{ - MutationRequest options; - options.expectedRevision = revision; - return options; -} - -/// @brief 工作副本中 Subject 的 access 位(classInfo 面)。 -ca::i32 subjectAccess(const WorkspaceBase& workspace) -{ - auto info = workspace.classInfo("workspace/Subject"); - EXPECT_TRUE(info.is_ok()); - return info.is_ok() ? std::move(info).unwrap().access : -1; -} - -/// @brief CP Utf8 条目文本(骨架解析面;非 Utf8/越界返回空)。 -std::string supportUtf8Name(const jbc::core::ClassFile& file, ca::u32 nameCp) -{ - return support::utf8Text(file, nameCp); -} - -/// @brief 工作副本中 Subject 的类级属性名集合(jbc IR 会话解析,与 decodeClass -/// 同一解析面;会话须在局部存活的 unique_ptr 中持有,backing 引用不可 -/// 绑到 unwrap 临时量上)。 -std::vector subjectAttributeNames(const WorkspaceBase& workspace) -{ - std::vector names; - auto content = workspace.readClassBytes("workspace/Subject"); - EXPECT_TRUE(content.is_ok()); - if (content.is_err()) return names; - auto parsed = jbc::ir::makeSessionFromBytes( - jbc::core::ByteView(std::move(content).unwrap().bytes)); - EXPECT_FALSE(parsed.is_err()); - if (parsed.is_err()) return names; - auto session = std::move(parsed).unwrap(); - const jbc::core::ClassFile& file = session->backing(); - for (const auto& slot : file.attrs) { - names.push_back(supportUtf8Name(file, slot.nameCp)); - } - return names; -} - -/// @brief ChangeClassAccess 便捷构造。 -ClassMutation changeAccess(std::vector setFlags, - std::vector clearFlags) -{ - ClassMutation mutation; - mutation.classUri = "workspace/Subject.class"; - mutation.kind = ClassMutationKind::ChangeClassAccess; - mutation.setFlags = std::move(setFlags); - mutation.clearFlags = std::move(clearFlags); - return mutation; -} - -bool applyOk(WorkspaceBase& workspace, const ClassMutation& mutation, ca::u64 revision) -{ - auto outcome = workspace.applyClassMutation(mutation, request(revision)); - return outcome.status.is_ok(); -} - -} // namespace - -TEST(MjClassMutationAccessTest, changeAccessKeepsUnmentionedBitsLikeAccSuper) -{ - WorkspaceBase workspace; - const auto input = writeAccessFixture("mjbase_access_change.jar"); - ASSERT_TRUE(workspace.openJar(input.string()).is_ok()); - // ".class public super" → 0x0021(public | super)。 - EXPECT_EQ(0x0021, subjectAccess(workspace)); - - // set final → 0x0031:super 位原样保留(P19 破坏性整体替换的对照)。 - ASSERT_TRUE(applyOk(workspace, changeAccess({"final"}, {}), 0)); - EXPECT_EQ(0x0031, subjectAccess(workspace)); - - // set abstract + clear final → 0x0421:提及位增删,未提及位(super)不动。 - ASSERT_TRUE(applyOk(workspace, changeAccess({"abstract"}, {"final"}), 1)); - EXPECT_EQ(0x0421, subjectAccess(workspace)); - - // 空名单 = 无操作(幂等直通)。 - ASSERT_TRUE(applyOk(workspace, changeAccess({}, {}), 2)); - EXPECT_EQ(0x0421, subjectAccess(workspace)); -} - -TEST(MjClassMutationAccessTest, deprecatedMapsToAttributeNotSilentlyDropped) -{ - WorkspaceBase workspace; - const auto input = writeAccessFixture("mjbase_access_deprecated.jar"); - ASSERT_TRUE(workspace.openJar(input.string()).is_ok()); - const auto before = subjectAttributeNames(workspace); - EXPECT_EQ(std::vector::difference_type(0), - std::count(before.begin(), before.end(), "Deprecated")); - - // change_access set deprecated → Deprecated 属性出现(access 位不变)。 - ASSERT_TRUE(applyOk(workspace, changeAccess({"deprecated"}, {}), 0)); - EXPECT_EQ(0x0021, subjectAccess(workspace)); - const auto added = subjectAttributeNames(workspace); - EXPECT_NE(std::count(added.begin(), added.end(), "Deprecated"), 0); - - // clear deprecated → 属性移除。 - ASSERT_TRUE(applyOk(workspace, changeAccess({}, {"deprecated"}), 1)); - const auto removed = subjectAttributeNames(workspace); - EXPECT_EQ(std::count(removed.begin(), removed.end(), "Deprecated"), 0); -} - -TEST(MjClassMutationAccessTest, legacySetClassAccessKeepsWholeReplaceAndMapsDeprecated) -{ - WorkspaceBase workspace; - const auto input = writeAccessFixture("mjbase_access_legacy.jar"); - ASSERT_TRUE(workspace.openJar(input.string()).is_ok()); - - // 旧字段旧语义(版本兼容契约):整体替换 access 位,未提及位丢弃照旧—— - // 旧工程重放按此解释,行为不漂移。 - ClassMutation legacy; - legacy.classUri = "workspace/Subject.class"; - legacy.kind = ClassMutationKind::SetClassAccess; - legacy.access = 0x0031; // public final super - ASSERT_TRUE(applyOk(workspace, legacy, 0)); - EXPECT_EQ(0x0031, subjectAccess(workspace)); - - legacy.access = 0x0001; // 只给 public:0x0030 丢弃(旧语义本身如此) - ASSERT_TRUE(applyOk(workspace, legacy, 1)); - EXPECT_EQ(0x0001, subjectAccess(workspace)); - - // 0x20000 deprecated 位:历史版本静默丢弃 → 现映射 Deprecated 属性(禁止 - // 静默丢弃;access 位内该位本就不是 flag)。 - legacy.access = 0x20001; // public + deprecated(属性) - ASSERT_TRUE(applyOk(workspace, legacy, 2)); - EXPECT_EQ(0x0001, subjectAccess(workspace)); - const auto attrs = subjectAttributeNames(workspace); - EXPECT_NE(std::count(attrs.begin(), attrs.end(), "Deprecated"), 0); -} - -TEST(MjClassMutationAccessTest, unknownFlagReportsLegalSetAndConflictsReject) -{ - WorkspaceBase workspace; - const auto input = writeAccessFixture("mjbase_access_unknown.jar"); - ASSERT_TRUE(workspace.openJar(input.string()).is_ok()); - - // 未知名报错并列出 class 目标合法 flag 全集。 - auto outcome = workspace.applyClassMutation(changeAccess({"sealedd"}, {}), - request(kAnyRevision)); - ASSERT_TRUE(outcome.status.is_err()); - const auto message = outcome.status.message; - EXPECT_NE(std::string::npos, message.find("unknown access flag")); - EXPECT_NE(std::string::npos, message.find("legal flags")); - EXPECT_NE(std::string::npos, message.find("sealed")); - EXPECT_NE(std::string::npos, message.find("deprecated")); - - // set 与 clear 相交拒绝。 - auto conflict = workspace.applyClassMutation(changeAccess({"final"}, {"final"}), - request(kAnyRevision)); - ASSERT_TRUE(conflict.status.is_err()); - EXPECT_NE(std::string::npos, conflict.status.message.find("both set and cleared")); - - // 结果态违约拒绝:interface 必须 abstract(现类非接口,set interface 落 - // interface&!abstract)。 - auto illegal = workspace.applyClassMutation(changeAccess({"interface"}, {}), - request(kAnyRevision)); - ASSERT_TRUE(illegal.status.is_err()); - EXPECT_NE(std::string::npos, illegal.status.message.find("interface must be abstract")); -} - -} // namespace mjbase::workspace diff --git a/mjbase/test/unittest/workspace/workspace_ledger_test.cpp b/mjbase/test/unittest/workspace/workspace_ledger_test.cpp deleted file mode 100644 index 03ab015..0000000 --- a/mjbase/test/unittest/workspace/workspace_ledger_test.cpp +++ /dev/null @@ -1,376 +0,0 @@ -// WorkspaceBase 台账换底(schema v2 交付 3):commit/undo/redo/revert_class 走 -// EditLedgerStore 的行为面——undo/redo 窗口与事务链一致、revert_class 反向应用、 -// savepoint = 书签(restore 记反向事务,undo 窗不受损)、bind → flush → 重开 -// 采纳(undo 跨 flush 存活)、手动压缩前后 restore 结果一致。 -// -// 断言面全部走 WorkspaceBase 公开签名(换底不外泄内部引擎)+ bundle 磁盘面 -// (project_store 清单原语);既有语义(revision 序列等)由 workspace_test.cpp -// 承载。契约点见 doc_ai/analysis-project-schema-v2.md §6(mjt 波次 2b 接线)。 -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "mjbase/jasm/jvm_jasm_service.hpp" -#include "mjbase/model/resource_container.hpp" -#include "mjbase/model/resource_helper.hpp" -#include "mjbase/project/edit_ledger_store.hpp" -#include "mjbase/project/project_store.hpp" -#include "mjbase/workspace/workspace.hpp" - -namespace mjbase::workspace { -namespace model = mjbase::model; - -namespace { - -ca::str::Utf16String utf16(const std::string& value) -{ - return ca::str::Utf16String::from_utf8_string(ca::str::Utf8StringRef::from_data( - reinterpret_cast(value.data()), value.size())); -} - -std::string classSource(const std::string& name, const std::string& value) -{ - return ".super java/lang/Object\n" - ".class public super " + - name + - " {\n" - " .field public counter I\n" - " .method public static value ()I {\n" - " code: {\n" - " bipush " + - value + - "\n" - " ireturn\n" - " }\n" - " }\n" - "}\n"; -} - -std::vector assemble(const std::string& name, const std::string& value) -{ - jasm::JvmJasmService service; - auto sourceName = utf16(name + ".jasm"); - auto source = utf16(classSource(name, value)); - auto result = service.assemble(sourceName.ref(), source.ref()); - if (!result.isOk()) throw std::runtime_error("workspace ledger fixture did not assemble"); - return result.value->bytes; -} - -/// 单类 JAR 夹具(workspace/Subject = )。 -std::filesystem::path writeLedgerFixture(const std::string& fileName, const std::string& value) -{ - model::ResourceContainer container; - const std::string uri = "workspace/Subject.class"; - container.addResource(uri, std::make_unique( - uri, assemble("workspace/Subject", value))); - const auto path = test::getTempOutPath(fileName); - model::ResourceHelper::exportToJar(container, path.string()); - return path; -} - -/// 清掉上一次 run 的同名临时 bundle,返回干净路径。 -std::filesystem::path freshTempDir(const std::string& name) -{ - const auto path = test::getTempOutPath(name); - std::error_code ec; - std::filesystem::remove_all(path, ec); - return path; -} - -MutationRequest expected(ca::u64 revision, bool dryRun = false) -{ - MutationRequest request; - request.expectedRevision = revision; - request.dryRun = dryRun; - return request; -} - -/// 工作副本中 Subject 类的原始字节(事务位一致性的比较基准)。 -std::vector subjectBytes(const WorkspaceBase& workspace) -{ - auto content = workspace.readClassBytes("workspace/Subject"); - EXPECT_TRUE(content.is_ok()); - return content.is_ok() ? std::move(content).unwrap().bytes : std::vector{}; -} - -/// bundle 清单中已登记的 section 名集合。 -std::vector registeredSections(const std::string& dirStr) -{ - auto loaded = project::readStoreManifestFile( - ca::str::Utf8StringRef::from_data(reinterpret_cast(dirStr.data()), - dirStr.size())); - EXPECT_FALSE(loaded.is_err()); - std::vector names; - if (loaded.is_ok()) { - for (const auto& entry : std::move(loaded).unwrap().sections) { - names.push_back(entry.name.to_std_string()); - } - } - return names; -} - -bool applySubjectValue(WorkspaceBase& workspace, ca::u64 revision, const std::string& value) -{ - auto sourceName = utf16("Subject.jasm"); - auto outcome = workspace.applyJasm("workspace/Subject.class", sourceName.ref(), - utf16(classSource("workspace/Subject", value)).ref(), - expected(revision)); - return outcome.status.is_ok(); -} - -} // namespace - -TEST(MjWorkspaceLedgerTest, commitUndoRedoWindowMatchesTransactionChain) -{ - WorkspaceBase workspace; - const auto input = writeLedgerFixture("mjbase_workspace_ledger_input.jar", "1"); - ASSERT_TRUE(workspace.openJar(input.string()).is_ok()); - const auto originalBytes = subjectBytes(workspace); - - ASSERT_TRUE(applySubjectValue(workspace, 0, "42")); - const auto afterFirstBytes = subjectBytes(workspace); - ASSERT_TRUE(applySubjectValue(workspace, 1, "43")); - EXPECT_EQ(2U, workspace.revision()); - const auto headBytes = subjectBytes(workspace); - EXPECT_NE(originalBytes, headBytes); - - // undo/redo 窗口:头指针回退/前进,字节精确回到对应事务位。 - auto undone = workspace.undo(expected(2)); - ASSERT_TRUE(undone.status.is_ok()); - EXPECT_EQ(afterFirstBytes, subjectBytes(workspace)); - EXPECT_TRUE(workspace.isDirty()); // 尚有一格未退到基线 - - ASSERT_TRUE(workspace.undo(expected(3)).status.is_ok()); - EXPECT_EQ(originalBytes, subjectBytes(workspace)); - EXPECT_FALSE(workspace.isDirty()); // 退到基线 = 干净 - - auto redone = workspace.redo(expected(4)); - ASSERT_TRUE(redone.status.is_ok()); - EXPECT_EQ(afterFirstBytes, subjectBytes(workspace)); - - ASSERT_TRUE(workspace.redo(expected(5)).status.is_ok()); - EXPECT_EQ(headBytes, subjectBytes(workspace)); - EXPECT_TRUE(workspace.isDirty()); - - // 新 commit 放弃 redo 尾分支(对齐旧 commit 清 redo 栈)。 - ASSERT_TRUE(applySubjectValue(workspace, 6, "99")); - auto deadRedo = workspace.redo(expected(7)); - ASSERT_FALSE(deadRedo.status.is_ok()); - EXPECT_EQ(WorkspaceError::FailedPrecondition, deadRedo.status.code); -} - -TEST(MjWorkspaceLedgerTest, revertClassRestoresOriginalBytesThroughLedger) -{ - WorkspaceBase workspace; - const auto input = writeLedgerFixture("mjbase_workspace_ledger_revert.jar", "7"); - ASSERT_TRUE(workspace.openJar(input.string()).is_ok()); - const auto originalBytes = subjectBytes(workspace); - - ASSERT_TRUE(applySubjectValue(workspace, 0, "8")); - const auto afterFirstBytes = subjectBytes(workspace); - ASSERT_TRUE(applySubjectValue(workspace, 1, "9")); - const auto afterSecondBytes = subjectBytes(workspace); - - // revert_class = 反向应用到原始字节(再走一次普通 commit,记新事务)。 - auto reverted = workspace.revertClass("workspace/Subject.class", expected(2)); - ASSERT_TRUE(reverted.status.is_ok()); - EXPECT_TRUE(reverted.committed); - EXPECT_EQ(originalBytes, subjectBytes(workspace)); - EXPECT_FALSE(workspace.isDirty()); // revert = 回到原始字节,不再偏离 - - // revert 本身可 undo(回到 revert 前头位),再逐级退回基线。 - auto undone = workspace.undo(expected(3)); - ASSERT_TRUE(undone.status.is_ok()); - EXPECT_EQ(afterSecondBytes, subjectBytes(workspace)); - ASSERT_TRUE(workspace.undo(expected(4)).status.is_ok()); - EXPECT_EQ(afterFirstBytes, subjectBytes(workspace)); - ASSERT_TRUE(workspace.undo(expected(5)).status.is_ok()); - EXPECT_EQ(originalBytes, subjectBytes(workspace)); -} - -TEST(MjWorkspaceLedgerTest, savepointRestoresAcrossLaterCommitsWithoutLosingUndo) -{ - WorkspaceBase workspace; - const auto input = writeLedgerFixture("mjbase_workspace_ledger_savepoint.jar", "1"); - ASSERT_TRUE(workspace.openJar(input.string()).is_ok()); - const auto originalBytes = subjectBytes(workspace); - - ASSERT_TRUE(workspace.createSavepoint("original").is_ok()); - ASSERT_TRUE(applySubjectValue(workspace, 0, "2")); - const auto afterFirstBytes = subjectBytes(workspace); - ASSERT_TRUE(applySubjectValue(workspace, 1, "3")); - const auto beforeRestoreBytes = subjectBytes(workspace); - - // restore = 物化书签位状态 + 普通提交(反向事务),undo 窗不受损。 - auto restored = workspace.restoreSavepoint("original", expected(2)); - ASSERT_TRUE(restored.status.is_ok()); - EXPECT_EQ(3U, restored.revision); - EXPECT_EQ(originalBytes, subjectBytes(workspace)); - - // 反向事务可 undo(回到 restore 前头位),再 redo 回 restore 后。 - auto undone = workspace.undo(expected(3)); - ASSERT_TRUE(undone.status.is_ok()); - EXPECT_EQ(beforeRestoreBytes, subjectBytes(workspace)); - auto redone = workspace.redo(expected(4)); - ASSERT_TRUE(redone.status.is_ok()); - EXPECT_EQ(originalBytes, subjectBytes(workspace)); - - // 未知名 savepoint 拒绝。 - auto missing = workspace.restoreSavepoint("never-was", expected(5)); - ASSERT_FALSE(missing.status.is_ok()); - EXPECT_EQ(WorkspaceError::NotFound, missing.status.code); -} - -TEST(MjWorkspaceLedgerTest, bindFlushReopenAdoptsHistoryAndKeepsUndoAlive) -{ - const auto jar = writeLedgerFixture("mjbase_workspace_ledger_bind.jar", "1"); - const auto dir = freshTempDir("mjbase_workspace_ledger_bind.mjt-project"); - const std::string dirStr = dir.string(); - ASSERT_FALSE(project::createBundleSkeleton(project::copyLedgerText(dirStr)).is_err()); - - { - WorkspaceBase workspace; - ASSERT_TRUE(workspace.openJar(jar.string()).is_ok()); - ASSERT_TRUE(workspace.bindLedgerStore(dirStr).is_ok()); - ASSERT_TRUE(applySubjectValue(workspace, 0, "2")); - ASSERT_TRUE(workspace.createSavepoint("edited").is_ok()); - ASSERT_TRUE(workspace.flushLedger().is_ok()); - EXPECT_EQ(1U, workspace.revision()); // flush/savepoint 不改 revision 计数 - } - // 磁盘面契约:flush 后清单带台账代 + overlay 载荷 section。 - const auto sections = registeredSections(dirStr); - bool ledgerSeen = false; - bool overlaySeen = false; - for (const auto& name : sections) { - if (name == "edit-ledger-1") ledgerSeen = true; - if (name == "overlay-1-0") overlaySeen = true; - } - EXPECT_TRUE(ledgerSeen); - EXPECT_TRUE(overlaySeen); - - // 新会话绑定同一 bundle:台账采纳(工作副本推进到头事务位),undo 跨 flush - // 存活,savepoint 可 restore。 - WorkspaceBase reopened; - ASSERT_TRUE(reopened.openJar(jar.string()).is_ok()); - const auto originalBytes = subjectBytes(reopened); - ASSERT_TRUE(reopened.bindLedgerStore(dirStr).is_ok()); - const auto adoptedBytes = subjectBytes(reopened); - EXPECT_NE(originalBytes, adoptedBytes); - EXPECT_EQ(0U, reopened.revision()); - - auto undone = reopened.undo(expected(0)); - ASSERT_TRUE(undone.status.is_ok()); - EXPECT_EQ(originalBytes, subjectBytes(reopened)); - - auto saved = reopened.restoreSavepoint("edited", expected(1)); - ASSERT_TRUE(saved.status.is_ok()); - EXPECT_EQ(adoptedBytes, subjectBytes(reopened)); -} - -TEST(MjWorkspaceLedgerTest, manualCompactionKeepsRestoreAndUndoWindowConsistent) -{ - const auto jar = writeLedgerFixture("mjbase_workspace_ledger_compact.jar", "1"); - const auto dir = freshTempDir("mjbase_workspace_ledger_compact.mjt-project"); - const std::string dirStr = dir.string(); - ASSERT_FALSE(project::createBundleSkeleton(project::copyLedgerText(dirStr)).is_err()); - - // 外提捕获:会话作用域在压缩续链 flush 后收口(单写者锁随对象释放)。 - std::vector headBytes; - std::vector bookmarkedBytes; - std::vector twoEditsBytes; - project::LedgerCompactRequest request; - request.keepUndoDepth = 2; - { - WorkspaceBase workspace; - ASSERT_TRUE(workspace.openJar(jar.string()).is_ok()); - ASSERT_TRUE(workspace.bindLedgerStore(dirStr).is_ok()); - const auto originalBytes = subjectBytes(workspace); - - // 四个编辑事务(txns 1..4)+ t2 位书签。 - const char* values[5] = {"1", "2", "3", "4", "5"}; - for (int i = 0; i < 4; ++i) { - ASSERT_TRUE(applySubjectValue(workspace, static_cast(i), values[i + 1])); - if (i == 1) { - ASSERT_TRUE(workspace.createSavepoint("keep-me").is_ok()); - twoEditsBytes = subjectBytes(workspace); - } - } - ASSERT_TRUE(workspace.flushLedger().is_ok()); - headBytes = subjectBytes(workspace); - - // 手动压缩:undo 承诺 2、书签全保留 → 保留点 = min(head-2, 书签位) = 2。 - auto compacted = workspace.compactLedger(request); - ASSERT_FALSE(compacted.is_err()); - const auto report = std::move(compacted).unwrap(); - EXPECT_TRUE(report.executed); - EXPECT_EQ(2U, report.retentionTxn); - EXPECT_TRUE(report.droppedBookmarks.empty()); - - // 磁盘面:新基线 + 新代台账已登记。 - const auto sections = registeredSections(dirStr); - bool baselineSeen = false; - bool newLedgerSeen = false; - for (const auto& name : sections) { - if (name == "edit-baseline-2") baselineSeen = true; - if (name == "edit-ledger-2") newLedgerSeen = true; - } - EXPECT_TRUE(baselineSeen); - EXPECT_TRUE(newLedgerSeen); - - // 压缩不变量 1:undo 承诺兑现——恰好两步回退到书签位,第三步拒绝。 - auto undoOne = workspace.undo(expected(4)); - ASSERT_TRUE(undoOne.status.is_ok()); - auto undoTwo = workspace.undo(expected(5)); - ASSERT_TRUE(undoTwo.status.is_ok()); - // 保留点 = 书签位(txn 2):两步 undo 停在书签位状态,而不是基线。 - EXPECT_EQ(twoEditsBytes, subjectBytes(workspace)); - EXPECT_NE(originalBytes, subjectBytes(workspace)); - auto undoThree = workspace.undo(expected(6)); - ASSERT_FALSE(undoThree.status.is_ok()); - EXPECT_EQ(WorkspaceError::FailedPrecondition, undoThree.status.code); - - // 压缩不变量 2:书签位状态不漂移(restore 越过压缩锚与被压缩区间)。 - auto redone = workspace.redo(expected(6)); - ASSERT_TRUE(redone.status.is_ok()); - auto redoneAgain = workspace.redo(expected(7)); - ASSERT_TRUE(redoneAgain.status.is_ok()); - EXPECT_EQ(headBytes, subjectBytes(workspace)); - auto saved = workspace.restoreSavepoint("keep-me", expected(8)); - ASSERT_TRUE(saved.status.is_ok()); - bookmarkedBytes = subjectBytes(workspace); - EXPECT_NE(headBytes, bookmarkedBytes); - - // 压缩后链继续生长(txn 5 = restore 反向事务),flush + 重开闭合。 - ASSERT_TRUE(applySubjectValue(workspace, 9, "6")); - ASSERT_TRUE(workspace.flushLedger().is_ok()); - } - - WorkspaceBase reopened; - ASSERT_TRUE(reopened.openJar(jar.string()).is_ok()); - auto bound = reopened.bindLedgerStore(dirStr); - if (bound.is_err()) { - const auto bindErr = std::move(bound).unwrap_err(); - FAIL() << "reopen bind: kind=" << static_cast(bindErr.code) << " " - << bindErr.message; - } - const auto finalBytes = subjectBytes(reopened); - EXPECT_NE(headBytes, finalBytes); - - // 重开后书签 restore 结果与压缩侧一致(书签跨压缩与跨会话存活)。 - auto restoredAgain = reopened.restoreSavepoint("keep-me", expected(0)); - ASSERT_TRUE(restoredAgain.status.is_ok()); - EXPECT_EQ(bookmarkedBytes, subjectBytes(reopened)); - - // 保留点不前移(undo 承诺 2 ≥ 头-锚距离的可达上界受书签约束)→ 拒绝再压。 - auto refused = reopened.compactLedger(request); - ASSERT_TRUE(refused.is_err()); - EXPECT_EQ(WorkspaceError::FailedPrecondition, std::move(refused).unwrap_err().code); -} - -} // namespace mjbase::workspace diff --git a/mjbase/test/unittest/workspace/workspace_supporting_role_test.cpp b/mjbase/test/unittest/workspace/workspace_supporting_role_test.cpp deleted file mode 100644 index 0a9246e..0000000 --- a/mjbase/test/unittest/workspace/workspace_supporting_role_test.cpp +++ /dev/null @@ -1,147 +0,0 @@ -// 角色化容器登记(P20)验收面:mount(role=library) → info 恰一条记录 → -// detach(support ID 唯一句柄)→ 零条。library/runtime 与遗留 support 共用 -// 同一在册表:路径/内容唯一约束跨角色生效,role 只是记录标签——不再出现 -// "support 半边 + library 半边"的双注册,也没有无 detach 句柄的残留条目。 -#include -#include - -#include -#include -#include -#include - -#include "mjbase/jasm/jvm_jasm_service.hpp" -#include "mjbase/model/container_stack.hpp" -#include "mjbase/model/resource_container.hpp" -#include "mjbase/model/resource_helper.hpp" -#include "mjbase/model/resource_model.hpp" -#include "mjbase/workspace/workspace.hpp" - -namespace mjbase::workspace { -namespace model = mjbase::model; - -namespace { - -ca::str::Utf16String utf16(const std::string& value) -{ - return ca::str::Utf16String::from_utf8_string(ca::str::Utf8StringRef::from_data( - reinterpret_cast(value.data()), value.size())); -} - -std::filesystem::path writeSubjectJar(const std::string& fileName) -{ - jasm::JvmJasmService service; - const std::string source = ".super java/lang/Object\n" - ".class public super workspace/Subject {\n" - " .method public static value ()I {\n" - " code: {\n" - " bipush 1\n" - " ireturn\n" - " }\n" - " }\n" - "}\n"; - auto result = service.assemble(utf16("Subject.jasm").ref(), utf16(source).ref()); - if (!result.isOk()) throw std::runtime_error("role fixture subject did not assemble"); - model::ResourceContainer container; - const std::string uri = "workspace/Subject.class"; - container.addResource(uri, - std::make_unique(uri, result.value->bytes)); - const auto path = test::getTempOutPath(fileName); - model::ResourceHelper::exportToJar(container, path.string()); - return path; -} - -std::filesystem::path writeLibraryJar(const std::string& fileName) -{ - jasm::JvmJasmService service; - const std::string source = ".super java/lang/Object\n" - ".class public super support/Library {\n" - "}\n"; - auto result = service.assemble(utf16("Library.jasm").ref(), utf16(source).ref()); - if (!result.isOk()) throw std::runtime_error("role fixture library did not assemble"); - model::ResourceContainer container; - const std::string uri = "support/Library.class"; - container.addResource(uri, - std::make_unique(uri, result.value->bytes)); - const auto path = test::getTempOutPath(fileName); - model::ResourceHelper::exportToJar(container, path.string()); - return path; -} - -/// 复制出同内容不同路径的 JAR(内容唯一约束的判别面)。 -std::filesystem::path copyJar(const std::filesystem::path& source, const std::string& fileName) -{ - const auto copy = test::getTempOutPath(fileName); - std::error_code error; - std::filesystem::remove(copy, error); - error.clear(); - std::filesystem::copy_file(source, copy, error); - if (error) throw std::runtime_error("role fixture copy failed"); - return copy; -} - -} // namespace - -TEST(MjWorkspaceSupportingRoleTest, libraryMountProducesOneRecordAndDetachClearsIt) -{ - WorkspaceBase workspace; - const auto primary = writeSubjectJar("mjbase_role_primary.jar"); - const auto library = writeLibraryJar("mjbase_role_library.jar"); - ASSERT_TRUE(workspace.openJar(primary.string()).is_ok()); - - // mount(role=library)→ info 恰一条记录,role 标签为 library。 - auto mounted = workspace.addSupportingContainer(library.string(), - model::ContainerRole::Library); - ASSERT_TRUE(mounted.is_ok()); - const auto mountedValue = std::move(mounted).unwrap(); - EXPECT_EQ("support-1", mountedValue.id); - EXPECT_EQ("library", mountedValue.role); - EXPECT_EQ(0U, mountedValue.fingerprint.find("fnv1a64:")); - - const auto listed = workspace.supportingResources(); - ASSERT_EQ(1U, listed.size()); - EXPECT_EQ("library", listed[0].role); - EXPECT_EQ(mountedValue.id, listed[0].id); - - // 换角色重挂同一路径/同内容:唯一约束跨角色生效,不追加第二条记录。 - auto samePath = workspace.addSupportingJar(library.string()); - ASSERT_TRUE(samePath.is_err()); - EXPECT_EQ(WorkspaceError::AlreadyExists, std::move(samePath).unwrap_err().code); - const auto copied = copyJar(library, "mjbase_role_library_copy.jar"); - auto sameContent = workspace.addSupportingContainer(copied.string(), - model::ContainerRole::Runtime); - ASSERT_TRUE(sameContent.is_err()); - EXPECT_EQ(WorkspaceError::AlreadyExists, std::move(sameContent).unwrap_err().code); - EXPECT_EQ(1U, workspace.supportingResources().size()); - - // detach(support ID 唯一句柄)→ 该容器全部角色注册一次性清理,零残留。 - auto detached = workspace.removeSupportingJar(mountedValue.id); - ASSERT_TRUE(detached.is_ok()); - EXPECT_EQ("library", std::move(detached).unwrap().role); - EXPECT_TRUE(workspace.supportingResources().empty()); -} - -TEST(MjWorkspaceSupportingRoleTest, primaryRoleRejectedAndLegacySupportUnchanged) -{ - WorkspaceBase workspace; - const auto primary = writeSubjectJar("mjbase_role_support_primary.jar"); - const auto library = writeLibraryJar("mjbase_role_support_lib.jar"); - ASSERT_TRUE(workspace.openJar(primary.string()).is_ok()); - - // primary 由 project_open 绑定,不接受挂载登记。 - auto rejected = workspace.addSupportingContainer(library.string(), - model::ContainerRole::Primary); - ASSERT_TRUE(rejected.is_err()); - EXPECT_EQ(WorkspaceError::InvalidArgument, std::move(rejected).unwrap_err().code); - EXPECT_TRUE(workspace.supportingResources().empty()); - - // 遗留 addSupportingJar 语义不变:缺省 role 标签 support。 - auto legacy = workspace.addSupportingJar(library.string()); - ASSERT_TRUE(legacy.is_ok()); - EXPECT_EQ("support", std::move(legacy).unwrap().role); - const auto listed = workspace.supportingResources(); - ASSERT_EQ(1U, listed.size()); - EXPECT_EQ("support", listed[0].role); -} - -} // namespace mjbase::workspace diff --git a/mjbase/test/unittest/workspace/workspace_test.cpp b/mjbase/test/unittest/workspace/workspace_test.cpp deleted file mode 100644 index 302261f..0000000 --- a/mjbase/test/unittest/workspace/workspace_test.cpp +++ /dev/null @@ -1,1879 +0,0 @@ -/// @file workspace_test.cpp -/// @brief WorkspaceBase 事务/查询面单测。 -/// -/// 夹具全部经 mjbase jasm 现汇编(零拷贝红线:无样本字节入库); -/// 错误通道 WorkspaceError;access 常量 workspace_types kAcc*。 -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "libca/core/tag_cast.hpp" - -#include "jbc/core/attribute.hpp" -#include "jbc/core/reader.hpp" -#include "jbc/core/writer.hpp" - -#include "mjbase/jasm/jvm_jasm_service.hpp" -#include "mjbase/model/resource_container.hpp" -#include "mjbase/model/resource_helper.hpp" -#include "mjbase/model/resource_model.hpp" -#include "mjbase/workspace/classfile_support.hpp" -#include "mjbase/workspace/summary_scan.hpp" -#include "mjbase/workspace/workspace.hpp" - -namespace mjbase::workspace { -namespace model = mjbase::model; - -namespace { - -ca::str::Utf16String utf16(const std::string& value) -{ - return ca::str::Utf16String::from_utf8_string(ca::str::Utf8StringRef::from_data( - reinterpret_cast(value.data()), value.size())); -} - -std::string classSource(const std::string& name, const std::string& value) -{ - return ".super java/lang/Object\n" - ".class public super " + - name + - " {\n" - " .field public counter I\n" - " .method public static value ()I {\n" - " code: {\n" - " bipush " + - value + - "\n" - " ireturn\n" - " }\n" - " }\n" - " .method public virtualValue ()I {\n" - " code: {\n" - " iconst_0\n" - " ireturn\n" - " }\n" - " }\n" - "}\n"; -} - -std::string callerSource() -{ - return ".super java/lang/Object\n" - ".class public super workspace/Caller {\n" - " .method public static call ()I {\n" - " code: {\n" - " invokestatic workspace/Subject.value ()I\n" - " ireturn\n" - " }\n" - " }\n" - "}\n"; -} - -std::string childSource(const std::string& superName) -{ - return ".super " + superName + "\n" - ".class public super workspace/Child {\n" - " .method public virtualValue ()I {\n" - " code: {\n" - " iconst_1\n" - " ireturn\n" - " }\n" - " }\n" - "}\n"; -} - -std::vector assemble(const std::string& name, const std::string& value) -{ - jasm::JvmJasmService service; - auto sourceName = utf16(name + ".jasm"); - auto source = utf16(classSource(name, value)); - auto result = service.assemble(sourceName.ref(), source.ref()); - if (!result.isOk()) - { - //夹具汇编失败把诊断消息带进异常,报错自描述(行号+原文)。 - std::string detail; - for (const auto& diagnostic : result.diagnostics) { - if (!detail.empty()) detail += "; "; - if (diagnostic.location && diagnostic.location->line > 0) - detail += "L" + std::to_string(diagnostic.location->line) + ": "; - detail += diagnostic.message.to_utf8_string().to_std_string(); - } - throw std::runtime_error("workspace fixture did not assemble: " + detail); - } - return result.value->bytes; -} - -std::vector assembleSource(const std::string& name, const std::string& sourceText) -{ - jasm::JvmJasmService service; - auto sourceName = utf16(name + ".jasm"); - auto source = utf16(sourceText); - auto result = service.assemble(sourceName.ref(), source.ref()); - if (!result.isOk()) - { - std::string detail; - for (const auto& diagnostic : result.diagnostics) { - if (!detail.empty()) detail += "; "; - if (diagnostic.location && diagnostic.location->line > 0) - detail += "L" + std::to_string(diagnostic.location->line) + ": "; - detail += diagnostic.message.to_utf8_string().to_std_string(); - } - throw std::runtime_error("workspace fixture did not assemble: " + detail); - } - return result.value->bytes; -} - -/// @brief 给 class 追加 Signature 属性(jbc 直改:typed SignatureAttr)。 -std::vector withClassSignature(const std::vector& bytes, - const std::string& signature) -{ - auto read = jbc::core::readClass(jbc::core::ByteView(bytes)); - if (read.is_err()) throw std::runtime_error("signature fixture read failed"); - auto parsed = std::move(read).unwrap(); - jbc::core::ClassFile& cf = *parsed.file; - auto sig = std::make_unique(); - sig->signatureCp = support::internUtf8(cf, signature); - cf.attrs.push_back(jbc::core::AttrSlot::makeNew(support::internUtf8(cf, "Signature"), - std::move(sig))); - auto written = jbc::core::writeClass(cf); - if (written.is_err()) throw std::runtime_error("signature fixture write failed"); - return std::move(written).unwrap(); -} - -std::filesystem::path writeLongSignatureFixture(const std::string& signature) -{ - model::ResourceContainer container; - const std::string uri = "workspace/LongSignature.class"; - container.addResource( - uri, - std::make_unique( - uri, withClassSignature(assemble("workspace/LongSignature", "1"), signature))); - const auto path = test::getTempOutPath("mjbase_workspace_long_signature.jar"); - model::ResourceHelper::exportToJar(container, path.string()); - return path; -} - -std::filesystem::path writeWorkspaceFixture() -{ - model::ResourceContainer container; - container.addResource("workspace/Subject.class", - std::make_unique( - "workspace/Subject.class", assemble("workspace/Subject", "1"))); - container.addResource("workspace/Caller.class", - std::make_unique( - "workspace/Caller.class", assembleSource("workspace/Caller", callerSource()))); - container.addResource("workspace/Child.class", - std::make_unique( - "workspace/Child.class", - assembleSource("workspace/Child", childSource("workspace/Subject")))); - container.addResource("data.txt", - std::make_unique( - "data.txt", - std::vector{'w', 'o', 'r', 'k', 's', 'p', 'a', 'c', 'e'})); - container.addResource("META-INF/MANIFEST.MF", - std::make_unique(mjbase::jar::Manifest::fromBytes( - {'M', 'a', 'n', 'i', 'f', 'e', 's', 't', '-', 'V', 'e', 'r', 's', 'i', 'o', 'n', - ':', ' ', '1', '.', '0', '\r', '\n', '\r', '\n'}))); - const auto path = test::getTempOutPath("mjbase_workspace_core_input.jar"); - model::ResourceHelper::exportToJar(container, path.string()); - return path; -} - -std::filesystem::path writeClassFixture(const std::string& fileName, - const std::string& internalName, - const std::string& value) -{ - model::ResourceContainer container; - const auto uri = internalName + ".class"; - container.addResource(uri, - std::make_unique(uri, assemble(internalName, value))); - const auto path = test::getTempOutPath(fileName); - model::ResourceHelper::exportToClassFile(container, path.string(), uri); - return path; -} - -/// Multi-Release 族夹具:base 与 versions/17 变体内名相同(workspace/Subject, -/// 字节内容一致),另带一个独立类保证容器非平凡。 -std::filesystem::path writeMultiReleaseFamilyFixture() -{ - model::ResourceContainer container; - const std::vector base = assemble("workspace/Subject", "1"); - container.addResource("workspace/Subject.class", - std::make_unique("workspace/Subject.class", - base)); - container.addResource("META-INF/versions/17/workspace/Subject.class", - std::make_unique( - "META-INF/versions/17/workspace/Subject.class", base)); - container.addResource("workspace/Caller.class", - std::make_unique( - "workspace/Caller.class", - assembleSource("workspace/Caller", callerSource()))); - const auto path = test::getTempOutPath("mjbase_workspace_mr_family_input.jar"); - model::ResourceHelper::exportToJar(container, path.string()); - return path; -} - -std::filesystem::path writeMalformedClassFixture() -{ - model::ResourceContainer container; - const std::string uri = "broken/Invalid.class"; - container.addResource(uri, - std::make_unique( - uri, std::vector{0xCA, 0xFE, 0xBA, 0xBE})); - const auto path = test::getTempOutPath("mjbase_workspace_invalid_input.class"); - model::ResourceHelper::exportToClassFile(container, path.string(), uri); - return path; -} - -std::filesystem::path writeSupportingFixture() -{ - model::ResourceContainer container; - container.addResource("support/Library.class", - std::make_unique( - "support/Library.class", - assembleSource("support/Library", - ".super workspace/Subject\n" - ".class public super support/Library {\n" - " .method public static call ()I {\n" - " code: {\n" - " invokestatic workspace/Subject.value ()I\n" - " ireturn\n" - " }\n" - " }\n" - " .method public virtualValue ()I {\n" - " code: {\n" - " iconst_2\n" - " ireturn\n" - " }\n" - " }\n" - "}\n"))); - const auto path = test::getTempOutPath("mjbase_workspace_core_support.jar"); - model::ResourceHelper::exportToJar(container, path.string()); - return path; -} - -std::filesystem::path writeRepairFixture() -{ - model::ResourceContainer container; - container.addResource("workspace/RepairSubject.class", - std::make_unique( - "workspace/RepairSubject.class", assemble("workspace/RepairSubject", "1"))); - // 照旧 import 分拣语义:broken/Truncated.class 带 4 字节 CAFEBABE 魔数, - // 导入器按魔数嗅探分拣为 ClassResource(jasm 验证失败 → invalid_class, - // 计入 classCount);broken/Invalid.class 3 字节 → UnknownResource → - // invalid_class_entry。 - container.addResource("broken/Invalid.class", - std::make_unique( - "broken/Invalid.class", std::vector{0xCA, 0xFE, 0xBA})); - container.addResource("broken/Truncated.class", - std::make_unique( - "broken/Truncated.class", - std::vector{0xCA, 0xFE, 0xBA, 0xBE})); - container.addResource("logs/corrupt_output_stream.txt", - std::make_unique( - "logs/corrupt_output_stream.txt", std::vector{'b', 'a', 'd'})); - const auto path = test::getTempOutPath("mjbase_workspace_repair_input.jar"); - model::ResourceHelper::exportToJar(container, path.string()); - return path; -} - -/// @brief Local 直通夹具:hold()I 真实占用 0 号局部槽;LVT 记录经 jbc 直改补入 -/// (mjbase jasm 不产 LVT;等价 javac 常规产物形态,全程程序化构造)。 -std::vector localHolderBytes() -{ - const auto source = ".super java/lang/Object\n" - ".class public super workspace/LocalHolder {\n" - " .method public static hold ()I {\n" - " code: {\n" - " bipush 7\n" - " istore 0\n" - " iload 0\n" - " ireturn\n" - " }\n" - " }\n" - "}\n"; - auto read = jbc::core::readClass( - jbc::core::ByteView(assembleSource("workspace/LocalHolder", source))); - if (read.is_err()) throw std::runtime_error("local holder fixture read failed"); - auto parsed = std::move(read).unwrap(); - jbc::core::ClassFile& cf = *parsed.file; - jbc::core::MemberInfo* hold = nullptr; - for (auto& method : cf.methods) { - if (support::utf8Text(cf, method.nameCp) == "hold" && - support::utf8Text(cf, method.descCp) == "()I") { - hold = &method; - break; - } - } - if (hold == nullptr) throw std::runtime_error("local holder method not found"); - auto code = support::ensureCode(cf, *hold); - if (code == nullptr) throw std::runtime_error("local holder code missing"); - - // LVT 行覆盖方法体全程:start=0(首指令)、length=code 长度、slot 0。 - jbc::core::LocalVariableTableAttr::Row row; - row.startPc = 0; - row.length = static_cast(code->code.size()); - row.nameCp = support::internUtf8(cf, "held"); - row.descCp = support::internUtf8(cf, "I"); - row.slot = 0; - const ca::u32 lvtName = support::internUtf8(cf, "LocalVariableTable"); - auto lvt = std::make_unique(); - lvt->rows.push_back(row); - code->attrs.push_back(jbc::core::AttrSlot::makeNew(lvtName, std::move(lvt))); - - auto written = jbc::core::writeClass(cf); - if (written.is_err()) throw std::runtime_error("local holder fixture write failed"); - return std::move(written).unwrap(); -} - -std::filesystem::path writeLocalHolderFixture() -{ - model::ResourceContainer container; - container.addResource("workspace/LocalHolder.class", - std::make_unique("workspace/LocalHolder.class", - localHolderBytes())); - const auto path = test::getTempOutPath("mjbase_workspace_local_holder_input.jar"); - model::ResourceHelper::exportToJar(container, path.string()); - return path; -} - -MutationRequest expected(ca::u64 revision, bool dryRun = false) -{ - MutationRequest request; - request.expectedRevision = revision; - request.dryRun = dryRun; - request.label = "workspace test"; - return request; -} - -} // namespace - -// ── 结构化查询 + supporting 生命周期 ─────────────────────────── - -TEST(MjWorkspaceCoreTest, ProvidesStructuredQueriesAndSupportingResources) -{ - WorkspaceBase workspace; - const auto input = writeWorkspaceFixture(); - ASSERT_TRUE(workspace.openJar(input.string()).is_ok()); - EXPECT_EQ(ArtifactKind::Jar, workspace.artifactKind()); - EXPECT_TRUE(workspace.primaryClassUri().empty()); - EXPECT_EQ(0U, workspace.revision()); - EXPECT_FALSE(workspace.isDirty()); - EXPECT_TRUE(workspace.canExport()); - - auto resources = workspace.listResources(); - ASSERT_TRUE(resources.is_ok()); - auto resourceList = std::move(resources).unwrap(); - ASSERT_GE(resourceList.size(), 3U); - EXPECT_NE(std::find_if(resourceList.begin(), resourceList.end(), - [](const ResourceSummary& resource) { - return resource.uri == "META-INF/MANIFEST.MF"; - }), - resourceList.end()); - - auto content = workspace.readResource("data.txt"); - ASSERT_TRUE(content.is_ok()); - auto contentValue = std::move(content).unwrap(); - EXPECT_EQ("resource", contentValue.summary.type); - EXPECT_EQ("workspace", std::string(contentValue.bytes.begin(), contentValue.bytes.end())); - - auto classBytes = workspace.readClassBytes("workspace/Subject"); - ASSERT_TRUE(classBytes.is_ok()); - const auto classBytesValue = std::move(classBytes).unwrap(); - EXPECT_EQ("workspace/Subject.class", classBytesValue.resourceUri); - EXPECT_EQ("workspace/Subject", classBytesValue.internalName); - EXPECT_EQ(0U, classBytesValue.fingerprint.find("fnv1a64:")); - ASSERT_GE(classBytesValue.bytes.size(), 4U); - EXPECT_EQ(0xcaU, static_cast(classBytesValue.bytes[0])); - EXPECT_EQ(0xfeU, static_cast(classBytesValue.bytes[1])); - EXPECT_EQ(0xbaU, static_cast(classBytesValue.bytes[2])); - EXPECT_EQ(0xbeU, static_cast(classBytesValue.bytes[3])); - - auto summaries = workspace.listClassSummaries(); - ASSERT_TRUE(summaries.is_ok()); - const auto summaryList = std::move(summaries).unwrap(); - ASSERT_EQ(3U, summaryList.size()); - const auto subjectSummary = - std::find_if(summaryList.begin(), summaryList.end(), [](const ClassSummary& summary) { - return summary.internalName == "workspace/Subject"; - }); - ASSERT_NE(subjectSummary, summaryList.end()); - EXPECT_EQ("workspace/Subject.class", subjectSummary->resourceUri); - EXPECT_EQ("java/lang/Object", subjectSummary->superName); - EXPECT_EQ(0U, subjectSummary->interfaceCount); - EXPECT_EQ(1U, subjectSummary->fieldCount); - EXPECT_EQ(2U, subjectSummary->methodCount); - - auto classes = workspace.listClasses(); - ASSERT_TRUE(classes.is_ok()); - auto classList = std::move(classes).unwrap(); - ASSERT_EQ(3U, classList.size()); - const auto subject = std::find_if(classList.begin(), classList.end(), [](const ClassInfo& info) { - return info.internalName == "workspace/Subject"; - }); - ASSERT_NE(subject, classList.end()); - ASSERT_EQ(2U, subject->methods.size()); - EXPECT_NE(std::find_if(subject->methods.begin(), subject->methods.end(), - [](const MethodInfo& method) { - return method.name == "value" && method.descriptor == "()I"; - }), - subject->methods.end()); - - auto method = workspace.methodInfo("workspace/Subject", "value", "()I"); - ASSERT_TRUE(method.is_ok()); - auto methodValue = std::move(method).unwrap(); - EXPECT_EQ(2U, methodValue.instructionCount); - EXPECT_EQ(0U, methodValue.tryCatchCount); - - auto field = workspace.fieldInfo("workspace/Subject", "counter", "I"); - ASSERT_TRUE(field.is_ok()); - EXPECT_EQ("counter", field.unwrap().name); - EXPECT_EQ("I", field.unwrap().descriptor); - - auto bytecode = workspace.methodBytecode("workspace/Subject", "value", "()I"); - ASSERT_TRUE(bytecode.is_ok()); - ASSERT_EQ(2U, bytecode.unwrap().instructions.size()); - EXPECT_EQ("ldc", bytecode.unwrap().instructions[0].mnemonic); - EXPECT_EQ("ireturn", bytecode.unwrap().instructions[1].mnemonic); - - auto methodJasm = workspace.getMethodJasm("workspace/Subject", "value", "()I"); - ASSERT_TRUE(methodJasm.isOk()); - EXPECT_NE( - std::string::npos, - methodJasm.value->source.to_utf8_string().to_std_string().find(".method public static value ()I")); - - auto manifest = workspace.manifestAttributes(); - ASSERT_TRUE(manifest.is_ok()); - ASSERT_EQ(1U, manifest.unwrap().size()); - EXPECT_EQ("Manifest-Version", manifest.unwrap()[0].key); - EXPECT_EQ("1.0", manifest.unwrap()[0].value); - - auto strings = workspace.resourceStrings(); - ASSERT_TRUE(strings.is_ok()); - auto stringValues = std::move(strings).unwrap(); - EXPECT_NE(std::find_if(stringValues.begin(), stringValues.end(), - [](const ResourceStringInfo& item) { - return item.uri == "data.txt" && item.value == "workspace"; - }), - stringValues.end()); - - auto methodXrefs = workspace.xrefsToMethod("workspace/Subject", "value", "()I"); - ASSERT_TRUE(methodXrefs.is_ok()); - auto methodXrefValues = std::move(methodXrefs).unwrap(); - ASSERT_EQ(1U, methodXrefValues.size()); - EXPECT_EQ("workspace/Caller", methodXrefValues[0].sourceClass); - EXPECT_EQ("workspace/Subject.value()I", methodXrefValues[0].target); - - auto directCallees = workspace.calleesOfMethod("workspace/Caller", "call", "()I"); - ASSERT_TRUE(directCallees.is_ok()); - ASSERT_EQ(1U, directCallees.unwrap().size()); - EXPECT_EQ("workspace/Subject.value()I", directCallees.unwrap()[0].target); - - auto relations = workspace.classRelations("workspace/Subject"); - ASSERT_TRUE(relations.is_ok()); - auto relationValue = std::move(relations).unwrap(); - EXPECT_NE(std::find(relationValue.superclasses.begin(), relationValue.superclasses.end(), - "java/lang/Object"), - relationValue.superclasses.end()); - EXPECT_EQ(std::vector({"workspace/Child"}), relationValue.directSubclasses); - - auto jasmText = workspace.getJasm("workspace/Subject.class"); - ASSERT_TRUE(jasmText.isOk()); - EXPECT_NE(std::string::npos, - jasmText.value->source.to_utf8_string().to_std_string().find("workspace/Subject")); - - const auto supportPath = writeSupportingFixture(); - auto supportAdded = workspace.addSupportingJar(supportPath.string()); - ASSERT_TRUE(supportAdded.is_ok()); - const auto supportAddedValue = std::move(supportAdded).unwrap(); - EXPECT_EQ("support-1", supportAddedValue.id); - EXPECT_EQ(0U, supportAddedValue.fingerprint.find("fnv1a64:")); - EXPECT_EQ(1U, supportAddedValue.classCount); - const auto supporting = workspace.supportingResources(); - ASSERT_EQ(1U, supporting.size()); - EXPECT_EQ("support-1", supporting[0].id); - EXPECT_EQ(supportAddedValue.fingerprint, supporting[0].fingerprint); - EXPECT_EQ(1U, supporting[0].classCount); - auto duplicateSupport = workspace.addSupportingJar(supportPath.string()); - ASSERT_TRUE(duplicateSupport.is_err()); - EXPECT_EQ(WorkspaceError::AlreadyExists, duplicateSupport.unwrap_err().code); - const auto supportCopy = test::getTempOutPath("mjbase_workspace_core_support_copy.jar"); - std::error_code copyError; - std::filesystem::remove(supportCopy, copyError); - copyError.clear(); - std::filesystem::copy_file(supportPath, supportCopy, copyError); - ASSERT_FALSE(copyError); - auto duplicateContent = workspace.addSupportingJar(supportCopy.string()); - ASSERT_TRUE(duplicateContent.is_err()); - EXPECT_EQ(WorkspaceError::AlreadyExists, duplicateContent.unwrap_err().code); - - auto relationsWithSupport = workspace.classRelations("workspace/Subject"); - ASSERT_TRUE(relationsWithSupport.is_ok()); - const auto relationsWithSupportValue = std::move(relationsWithSupport).unwrap(); - EXPECT_NE(std::find(relationsWithSupportValue.allSubclasses.begin(), - relationsWithSupportValue.allSubclasses.end(), "support/Library"), - relationsWithSupportValue.allSubclasses.end()); - const auto supportRelation = - std::find_if(relationsWithSupportValue.sources.begin(), - relationsWithSupportValue.sources.end(), - [](const ClassRelationSource& source) { - return source.internalName == "support/Library"; - }); - ASSERT_NE(supportRelation, relationsWithSupportValue.sources.end()); - EXPECT_TRUE(supportRelation->supporting); - EXPECT_EQ("support-1", supportRelation->supportId); - auto supportCallers = workspace.xrefsToMethod("workspace/Subject", "value", "()I"); - ASSERT_TRUE(supportCallers.is_ok()); - const auto supportCallerValues = std::move(supportCallers).unwrap(); - const auto supportCaller = - std::find_if(supportCallerValues.begin(), supportCallerValues.end(), - [](const ReferenceInfo& reference) { - return reference.sourceClass == "support/Library"; - }); - ASSERT_NE(supportCaller, supportCallerValues.end()); - EXPECT_TRUE(supportCaller->supporting); - EXPECT_EQ("support-1", supportCaller->supportId); - auto supportOverrides = - workspace.overridesOfMethod("workspace/Subject", "virtualValue", "()I"); - ASSERT_TRUE(supportOverrides.is_ok()); - const auto supportOverrideValues = std::move(supportOverrides).unwrap(); - const auto supportOverride = - std::find_if(supportOverrideValues.begin(), supportOverrideValues.end(), - [](const MethodOverrideInfo& overrideInfo) { - return overrideInfo.owner == "support/Library"; - }); - ASSERT_NE(supportOverride, supportOverrideValues.end()); - EXPECT_TRUE(supportOverride->supporting); - EXPECT_EQ("support-1", supportOverride->supportId); - - auto removedSupport = workspace.removeSupportingJar("support-1"); - ASSERT_TRUE(removedSupport.is_ok()); - EXPECT_EQ(supportAddedValue.fingerprint, removedSupport.unwrap().fingerprint); - EXPECT_TRUE(workspace.supportingResources().empty()); - auto relationsWithoutSupport = workspace.classRelations("workspace/Subject"); - ASSERT_TRUE(relationsWithoutSupport.is_ok()); - const auto relationsWithoutSupportValue = std::move(relationsWithoutSupport).unwrap(); - EXPECT_EQ(relationsWithoutSupportValue.allSubclasses.end(), - std::find(relationsWithoutSupportValue.allSubclasses.begin(), - relationsWithoutSupportValue.allSubclasses.end(), "support/Library")); - auto missingSupport = workspace.removeSupportingJar("support-1"); - ASSERT_TRUE(missingSupport.is_err()); - EXPECT_EQ(WorkspaceError::NotFound, missingSupport.unwrap_err().code); - - const auto damagedSupport = test::getTempOutPath("mjbase_workspace_damaged_support.jar"); - std::ofstream(damagedSupport, std::ios::binary | std::ios::trunc) << "not a jar"; - auto damaged = workspace.addSupportingJar(damagedSupport.string()); - ASSERT_TRUE(damaged.is_err()); - EXPECT_EQ(WorkspaceError::DataLoss, damaged.unwrap_err().code); -} - -TEST(MjWorkspaceCoreTest, SurfacesReflectionClassStringsAsTypeReferences) -{ - // class-string 元数据事实:形态合法的字符串常量按 remapper 反射语法识别为 - // 类型引用候选(remap 改的 xref 必须可见,xref_class 契约 "metadata - // references" 的 mjbase 对应物)。点分形态归一为内部名。 - model::ResourceContainer container; - container.addResource("workspace/Reflector.class", - std::make_unique( - "workspace/Reflector.class", - assembleSource("workspace/Reflector", - ".super java/lang/Object\n" - ".class public super workspace/Reflector {\n" - " .method public static load ()V {\n" - " code: {\n" - " ldc \"workspace.Subject\"\n" - " invokestatic java/lang/Class.forName " - "(Ljava/lang/String;)Ljava/lang/Class;\n" - " pop\n" - " return\n" - " }\n" - " }\n" - "}\n"))); - const auto path = test::getTempOutPath("mjbase_workspace_reflection_input.jar"); - model::ResourceHelper::exportToJar(container, path.string()); - - WorkspaceBase workspace; - ASSERT_TRUE(workspace.openJar(path.string()).is_ok()); - auto xrefs = workspace.xrefsToClass("workspace/Subject"); - ASSERT_TRUE(xrefs.is_ok()); - const auto values = std::move(xrefs).unwrap(); - ASSERT_EQ(1U, values.size()); - EXPECT_EQ("workspace/Reflector", values[0].sourceClass); - EXPECT_EQ("workspace/Subject", values[0].target); - EXPECT_EQ("type", values[0].kind); - EXPECT_EQ("load", values[0].sourceMethod); -} - -TEST(MjWorkspaceCoreTest, KeepsLargeSignaturesOutOfClassSummaries) -{ - const std::string signature = - "Ljava/lang/Object;"; - WorkspaceBase workspace; - ASSERT_TRUE(workspace.openJar(writeLongSignatureFixture(signature).string()).is_ok()); - - auto summaries = workspace.listClassSummaries(); - ASSERT_TRUE(summaries.is_ok()); - const auto summaryValues = std::move(summaries).unwrap(); - ASSERT_EQ(1U, summaryValues.size()); - EXPECT_EQ("workspace/LongSignature.class", summaryValues[0].resourceUri); - EXPECT_EQ("workspace/LongSignature", summaryValues[0].internalName); - EXPECT_EQ(1U, summaryValues[0].fieldCount); - EXPECT_EQ(2U, summaryValues[0].methodCount); - - auto detail = workspace.classInfo("workspace/LongSignature"); - ASSERT_TRUE(detail.is_ok()); - EXPECT_EQ(signature, detail.unwrap().signature); -} - -// ── 单 class 工作区 ───────────────────────────────────── - -TEST(MjWorkspaceCoreTest, OpensEditsUndoesAndExportsSingleClass) -{ - WorkspaceBase workspace; - const auto input = writeClassFixture("mjbase_workspace_class_input.class", - "workspace/SingleSubject", "1"); - ASSERT_TRUE(workspace.openClass(input.string()).is_ok()); - EXPECT_TRUE(workspace.isLoaded()); - EXPECT_EQ(ArtifactKind::Class, workspace.artifactKind()); - EXPECT_EQ("workspace/SingleSubject.class", workspace.primaryClassUri()); - EXPECT_EQ(input.string(), workspace.sourcePath()); - EXPECT_EQ(0U, workspace.revision()); - EXPECT_FALSE(workspace.isDirty()); - - auto resources = workspace.listResources(); - ASSERT_TRUE(resources.is_ok()); - ASSERT_EQ(1U, resources.unwrap().size()); - EXPECT_EQ(workspace.primaryClassUri(), resources.unwrap()[0].uri); - EXPECT_TRUE(workspace.classInfo("workspace/SingleSubject").is_ok()); - EXPECT_TRUE(workspace.methodBytecode("workspace/SingleSubject", "value", "()I").is_ok()); - auto originalJasm = workspace.getJasm(workspace.primaryClassUri()); - ASSERT_TRUE(originalJasm.isOk()); - const auto originalSource = originalJasm.value->source.to_utf8_string().to_std_string(); - - auto sourceName = utf16("SingleSubject.jasm"); - auto replacement = utf16(classSource("workspace/SingleSubject", "42")); - auto stale = workspace.applyJasm(workspace.primaryClassUri(), sourceName.ref(), - replacement.ref(), expected(1)); - EXPECT_EQ(WorkspaceError::Aborted, stale.status.code); - EXPECT_EQ(0U, workspace.revision()); - - auto applied = workspace.applyJasm(workspace.primaryClassUri(), sourceName.ref(), - replacement.ref(), expected(0)); - ASSERT_TRUE(applied.status.is_ok()); - EXPECT_TRUE(applied.committed); - EXPECT_EQ(1U, applied.revision); - EXPECT_TRUE(workspace.isDirty()); - - const auto undone = workspace.undo(expected(1)); - ASSERT_TRUE(undone.status.is_ok()); - EXPECT_EQ(2U, undone.revision); - EXPECT_FALSE(workspace.isDirty()); - const auto redone = workspace.redo(expected(2)); - ASSERT_TRUE(redone.status.is_ok()); - EXPECT_EQ(3U, redone.revision); - EXPECT_TRUE(workspace.isDirty()); - - EXPECT_EQ(WorkspaceError::PermissionDenied, - workspace.exportClass(input.string()).unwrap_err().code); - EXPECT_EQ(WorkspaceError::FailedPrecondition, - workspace.exportJar(test::getTempOutPath("class_as_jar.jar").string()) - .unwrap_err() - .code); - const auto output = test::getTempOutPath("mjbase_workspace_class_output.class"); - std::error_code filesystemError; - std::filesystem::remove(output, filesystemError); - ASSERT_TRUE(workspace.exportClass(output.string()).is_ok()); - EXPECT_TRUE(std::filesystem::exists(output)); - EXPECT_EQ(WorkspaceError::AlreadyExists, - workspace.exportClass(output.string()).unwrap_err().code); - - WorkspaceBase exported; - ASSERT_TRUE(exported.openClass(output.string()).is_ok()); - auto exportedJasm = exported.getJasm(exported.primaryClassUri()); - ASSERT_TRUE(exportedJasm.isOk()); - EXPECT_NE(originalSource, exportedJasm.value->source.to_utf8_string().to_std_string()); - - WorkspaceBase original; - ASSERT_TRUE(original.openClass(input.string()).is_ok()); - auto reopenedOriginalJasm = original.getJasm(original.primaryClassUri()); - ASSERT_TRUE(reopenedOriginalJasm.isOk()); - EXPECT_EQ(originalSource, - reopenedOriginalJasm.value->source.to_utf8_string().to_std_string()); -} - -TEST(MjWorkspaceCoreTest, RejectsInvalidSingleClassWithoutChangingLoadedState) -{ - WorkspaceBase workspace; - const auto input = writeClassFixture("mjbase_workspace_class_state_input.class", - "workspace/StableSubject", "1"); - ASSERT_TRUE(workspace.openClass(input.string()).is_ok()); - auto sourceName = utf16("StableSubject.jasm"); - auto replacement = utf16(classSource("workspace/StableSubject", "42")); - ASSERT_TRUE(workspace - .applyJasm(workspace.primaryClassUri(), sourceName.ref(), replacement.ref(), - expected(0)) - .status.is_ok()); - auto editedJasm = workspace.getJasm(workspace.primaryClassUri()); - ASSERT_TRUE(editedJasm.isOk()); - const auto editedSource = editedJasm.value->source.to_utf8_string().to_std_string(); - - auto invalidJasm = utf16(classSource("workspace/StableSubject", "1\n return")); - const auto rejectedEdit = workspace.applyJasm( - workspace.primaryClassUri(), sourceName.ref(), invalidJasm.ref(), expected(1)); - EXPECT_EQ(WorkspaceError::InvalidArgument, rejectedEdit.status.code); - EXPECT_EQ(1U, workspace.revision()); - - const auto malformed = writeMalformedClassFixture(); - EXPECT_EQ(WorkspaceError::DataLoss, - workspace.openClass(malformed.string()).unwrap_err().code); - EXPECT_EQ(ArtifactKind::Class, workspace.artifactKind()); - EXPECT_EQ(input.string(), workspace.sourcePath()); - EXPECT_EQ("workspace/StableSubject.class", workspace.primaryClassUri()); - EXPECT_EQ(1U, workspace.revision()); - EXPECT_TRUE(workspace.isDirty()); - auto currentJasm = workspace.getJasm(workspace.primaryClassUri()); - ASSERT_TRUE(currentJasm.isOk()); - EXPECT_EQ(editedSource, currentJasm.value->source.to_utf8_string().to_std_string()); -} - -TEST(MjWorkspaceCoreTest, OpensAndExportsUtf8ArtifactPaths) -{ - namespace fs = std::filesystem; - const auto directory = test::getTempOutPath("mjbase_utf8_artifacts").parent_path() / - fs::u8path(u8"含 空格目录"); - std::error_code filesystemError; - fs::create_directories(directory, filesystemError); - ASSERT_FALSE(filesystemError); - - const auto sourceClass = - writeClassFixture("mjbase_utf8_source.class", "workspace/Utf8Subject", "1"); - const auto utf8Class = directory / fs::u8path(u8"输入样本.class"); - fs::copy_file(sourceClass, utf8Class, fs::copy_options::overwrite_existing, filesystemError); - ASSERT_FALSE(filesystemError); - - WorkspaceBase classWorkspace; - ASSERT_TRUE(classWorkspace.openClass(utf8Class.u8string()).is_ok()); - EXPECT_EQ(utf8Class.u8string(), classWorkspace.sourcePath()); - const auto exportedClass = directory / fs::u8path(u8"导出样本.class"); - fs::remove(exportedClass, filesystemError); - ASSERT_TRUE(classWorkspace.exportClass(exportedClass.u8string()).is_ok()); - EXPECT_TRUE(fs::exists(exportedClass)); - - const auto sourceJar = writeWorkspaceFixture(); - const auto utf8Jar = directory / fs::u8path(u8"输入样本.jar"); - fs::copy_file(sourceJar, utf8Jar, fs::copy_options::overwrite_existing, filesystemError); - ASSERT_FALSE(filesystemError); - - WorkspaceBase jarWorkspace; - ASSERT_TRUE(jarWorkspace.openJar(utf8Jar.u8string()).is_ok()); - EXPECT_EQ(utf8Jar.u8string(), jarWorkspace.sourcePath()); - const auto exportedJar = directory / fs::u8path(u8"导出样本.jar"); - fs::remove(exportedJar, filesystemError); - ASSERT_TRUE(jarWorkspace.exportJar(exportedJar.u8string()).is_ok()); - EXPECT_TRUE(fs::exists(exportedJar)); -} - -// ── 搜索面 ──────────────────────────────────────────────── - -TEST(MjWorkspaceCoreTest, SearchesClassesMembersConstantsAndInstructions) -{ - WorkspaceBase workspace; - ASSERT_TRUE(workspace.openJar(writeWorkspaceFixture().string()).is_ok()); - - auto classes = workspace.searchClasses("subject"); - ASSERT_TRUE(classes.is_ok()); - auto classHits = std::move(classes).unwrap(); - EXPECT_NE(std::find_if(classHits.begin(), classHits.end(), [](const SearchHit& hit) { - return hit.kind == "class" && hit.owner == "workspace/Subject"; - }), - classHits.end()); - - SearchOptions caseSensitive; - caseSensitive.caseSensitive = true; - auto caseSensitiveClasses = workspace.searchClasses("subject", caseSensitive); - ASSERT_TRUE(caseSensitiveClasses.is_ok()); - EXPECT_TRUE(caseSensitiveClasses.unwrap().empty()); - - auto members = workspace.searchMembers("counter"); - ASSERT_TRUE(members.is_ok()); - auto memberHits = std::move(members).unwrap(); - EXPECT_NE(std::find_if(memberHits.begin(), memberHits.end(), [](const SearchHit& hit) { - return hit.kind == "field" && hit.owner == "workspace/Subject" && - hit.name == "counter" && hit.descriptor == "I"; - }), - memberHits.end()); - - auto strings = workspace.searchStrings("workspace/Subject"); - ASSERT_TRUE(strings.is_ok()); - auto stringHits = std::move(strings).unwrap(); - EXPECT_NE(std::find_if(stringHits.begin(), stringHits.end(), [](const SearchHit& hit) { - return hit.resourceUri == "workspace/Subject.class" && - hit.value == "workspace/Subject"; - }), - stringHits.end()); - - auto numbers = workspace.searchNumbers("1.0"); - ASSERT_TRUE(numbers.is_ok()); - auto numberHits = std::move(numbers).unwrap(); - EXPECT_NE(std::find_if(numberHits.begin(), numberHits.end(), [](const SearchHit& hit) { - return hit.kind == "instruction_number" && hit.owner == "workspace/Subject" && - hit.name == "value" && hit.value == "1"; - }), - numberHits.end()); - - auto instructions = workspace.searchInstructions("invokestatic"); - ASSERT_TRUE(instructions.is_ok()); - auto instructionHits = std::move(instructions).unwrap(); - EXPECT_NE(std::find_if(instructionHits.begin(), instructionHits.end(), - [](const SearchHit& hit) { - return hit.kind == "instruction" && hit.owner == "workspace/Caller" && - hit.name == "call" && - hit.value.find("workspace/Subject.value()I") != - std::string::npos; - }), - instructionHits.end()); - - SearchOptions exactClass; - exactClass.matchMode = SearchMatchMode::Exact; - auto exactClasses = workspace.searchClasses("workspace/Subject", exactClass); - ASSERT_TRUE(exactClasses.is_ok()); - ASSERT_EQ(1U, exactClasses.unwrap().size()); - - SearchOptions regexClass; - regexClass.matchMode = SearchMatchMode::Regex; - regexClass.caseSensitive = true; - regexClass.packagePrefix = "workspace/"; - auto regexClasses = - workspace.searchClasses("^workspace/(Subject|Caller)$", regexClass); - ASSERT_TRUE(regexClasses.is_ok()); - EXPECT_EQ(2U, regexClasses.unwrap().size()); - - SearchOptions fieldOnly; - fieldOnly.memberKind = "field"; - fieldOnly.owner = "workspace/Subject"; - auto filteredMembers = workspace.searchMembers("counter", fieldOnly); - ASSERT_TRUE(filteredMembers.is_ok()); - ASSERT_EQ(1U, filteredMembers.unwrap().size()); - EXPECT_EQ("field", filteredMembers.unwrap()[0].kind); - - SearchOptions invokeStaticOnly; - invokeStaticOnly.opcode = 184; - invokeStaticOnly.owner = "workspace/Caller"; - auto filteredInstructions = - workspace.searchInstructions("workspace/Subject", invokeStaticOnly); - ASSERT_TRUE(filteredInstructions.is_ok()); - ASSERT_EQ(1U, filteredInstructions.unwrap().size()); - - SearchOptions invalidRegex; - invalidRegex.matchMode = SearchMatchMode::Regex; - auto malformedRegex = workspace.searchClasses("[", invalidRegex); - ASSERT_TRUE(malformedRegex.is_err()); - EXPECT_EQ(WorkspaceError::InvalidArgument, malformedRegex.unwrap_err().code); - - auto emptyQuery = workspace.searchStrings(""); - ASSERT_TRUE(emptyQuery.is_err()); - EXPECT_EQ(WorkspaceError::InvalidArgument, emptyQuery.unwrap_err().code); - - auto invalidNumber = workspace.searchNumbers("not-a-number"); - ASSERT_TRUE(invalidNumber.is_err()); - EXPECT_EQ(WorkspaceError::InvalidArgument, invalidNumber.unwrap_err().code); -} - -TEST(MjWorkspaceCoreTest, ClassifiesInstructionNodesAndDedupsStringBackedUtf8) -{ - //评估反馈三连回归:①CP 形态指令 node_type 按语义归类(旧布局命名把 - //getstatic/invoke*/checkcast 全标 ldc);②字段引用操作数 owner.name:desc - //冒号分界(方法描述符 '(' 自带分界,保持扁平);③ldc 字面量的背书 utf8 - //不再以裸 utf8 行与 constant_pool_string 重复成行。 - model::ResourceContainer container; - container.addResource( - "workspace/Render.class", - std::make_unique( - "workspace/Render.class", - assembleSource("workspace/Render", - ".super java/lang/Object\n" - ".class public super workspace/Render {\n" - " .method public static render ()V {\n" - " code: {\n" - " getstatic java/lang/System.out Ljava/io/PrintStream;\n" - " ldc \"demo-payload\"\n" - " invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V\n" - " ldc \"str\"\n" - " checkcast java/lang/String\n" - " pop\n" - " goto A\n" - " A:\n" - " return\n" - " }\n" - " }\n" - " .method public static pick ([Ljava/lang/String;I)Ljava/lang/String; {\n" - " parameters: { p0, p1 },\n" - " code: {\n" - " aload p0\n" - " iload p1\n" - " aaload\n" - " areturn\n" - " }\n" - " }\n" - " .method public static zero ()I {\n" - " code: {\n" - " iconst_0\n" - " ireturn\n" - " }\n" - " }\n" - "}\n"))); - const auto path = test::getTempOutPath("mjbase_workspace_instruction_nodes.jar"); - model::ResourceHelper::exportToJar(container, path.string()); - - WorkspaceBase workspace; - ASSERT_TRUE(workspace.openJar(path.string()).is_ok()); - - const auto valueOf = [&workspace](const char* query) { - auto found = workspace.searchInstructions(query); - if (!found.is_ok()) return std::string{}; - auto hits = std::move(found).unwrap(); - if (hits.size() != 1) return std::string{}; - return hits.front().value; - }; - EXPECT_EQ("getstatic field 178 java/lang/System.out:Ljava/io/PrintStream;", - valueOf("getstatic")); - EXPECT_EQ("invokevirtual invoke 182 java/io/PrintStream.println(Ljava/lang/String;)V", - valueOf("invokevirtual")); - EXPECT_EQ("checkcast cp_ref 192 java/lang/String", valueOf("checkcast")); - EXPECT_EQ("goto jump 167", valueOf("goto")); - //aload/astore 裸名是数组族,带槽位短格式是局部变量:剥后缀判定不得 - //让 aload_N 误入数组表。 - EXPECT_EQ("aload_0 var 42", valueOf("aload_0")); - EXPECT_EQ("iload_1 var 27", valueOf("iload_1")); - EXPECT_EQ("aaload array 50", valueOf("aaload")); - EXPECT_EQ("iconst_0 const 3", valueOf("iconst_0")); - - auto ldcHits = workspace.searchInstructions("demo-payload"); - ASSERT_TRUE(ldcHits.is_ok()); - auto ldcValues = std::move(ldcHits).unwrap(); - ASSERT_EQ(1U, ldcValues.size()); - EXPECT_EQ("ldc ldc 18 demo-payload", ldcValues.front().value); - - auto bytecode = workspace.methodBytecode("workspace/Render", "render", "()V"); - ASSERT_TRUE(bytecode.is_ok()); - const auto bcValue = std::move(bytecode).unwrap(); - ASSERT_EQ(8, static_cast(bcValue.instructions.size())); - EXPECT_EQ("field", bcValue.instructions[0].nodeType); - EXPECT_EQ("ldc", bcValue.instructions[1].nodeType); - EXPECT_EQ("invoke", bcValue.instructions[2].nodeType); - EXPECT_EQ("cp_ref", bcValue.instructions[4].nodeType); - EXPECT_EQ("insn", bcValue.instructions[5].nodeType); - EXPECT_EQ("jump", bcValue.instructions[6].nodeType); - EXPECT_EQ("insn", bcValue.instructions[7].nodeType); - - //去重面:字面量只剩 constant_pool_string 一行;非 String 背书的 utf8 - //(类引用名)照旧以 constant_pool_utf8 呈现。 - auto literals = workspace.searchStrings("demo-payload"); - ASSERT_TRUE(literals.is_ok()); - auto literalHits = std::move(literals).unwrap(); - ASSERT_EQ(1U, literalHits.size()); - EXPECT_EQ("constant_pool_string", literalHits.front().kind); - - auto classRefs = workspace.searchStrings("java/lang/System"); - ASSERT_TRUE(classRefs.is_ok()); - bool sawUtf8ClassRef = false; - for (const auto& hit : std::move(classRefs).unwrap()) { - if (hit.kind == "constant_pool_utf8" && hit.value == "java/lang/System") - sawUtf8ClassRef = true; - } - EXPECT_TRUE(sawUtf8ClassRef); -} - -// ── 候选 JAR 提交协议(test 8,等价改写:候选经 applyClassBytes 构造)──── - -TEST(MjWorkspaceCoreTest, CommitsPrivateWorkflowCandidateJarAtomically) -{ - const auto input = writeWorkspaceFixture(); - WorkspaceBase candidateWorkspace; - ASSERT_TRUE(candidateWorkspace.openJar(input.string()).is_ok()); - // 候选产物:两次 applyClassBytes 等价构造 - // (类内部名改写 + - // Child super 同步改指),候选 JAR 协议断言逐条保留。 - auto renamedSubject = assembleSource("workspace/RenamedSubject", - classSource("workspace/RenamedSubject", "1")); - auto renamedChild = assembleSource("workspace/Child", childSource("workspace/RenamedSubject")); - ASSERT_TRUE(candidateWorkspace - .applyClassBytes("workspace/Subject.class", renamedSubject, expected(0)) - .status.is_ok()); - ASSERT_TRUE(candidateWorkspace - .applyClassBytes("workspace/Child.class", renamedChild, expected(1)) - .status.is_ok()); - - const auto candidateJar = test::getTempOutPath("mjbase_workspace_private_candidate.jar"); - const auto exportedJar = test::getTempOutPath("mjbase_workspace_candidate_export.jar"); - std::error_code filesystemError; - std::filesystem::remove(candidateJar, filesystemError); - std::filesystem::remove(exportedJar, filesystemError); - ASSERT_TRUE(candidateWorkspace.exportJar(candidateJar.string()).is_ok()); - - WorkspaceBase workspace; - ASSERT_TRUE(workspace.openJar(input.string()).is_ok()); - WorkflowCandidateCommitRequest request; - request.candidateJarPath = candidateJar.string(); - request.exportPath = exportedJar.string(); - - const auto preview = workspace.commitWorkflowCandidateJar(request, expected(0, true)); - ASSERT_TRUE(preview.mutation.status.is_ok()); - EXPECT_FALSE(preview.mutation.committed); - EXPECT_EQ(0U, preview.mutation.revision); - EXPECT_FALSE(preview.exported); - EXPECT_EQ(std::filesystem::absolute(exportedJar).lexically_normal().string(), - preview.exportPath); - EXPECT_FALSE(std::filesystem::exists(exportedJar)); - ASSERT_TRUE(workspace.classInfo("workspace/Subject").is_ok()); - EXPECT_TRUE(workspace.classInfo("workspace/RenamedSubject").is_err()); - - auto invalidExport = request; - invalidExport.exportPath = input.string(); - const auto rejected = workspace.commitWorkflowCandidateJar(invalidExport, expected(0)); - EXPECT_TRUE(rejected.mutation.status.is_err()); - EXPECT_FALSE(rejected.mutation.committed); - EXPECT_FALSE(rejected.exported); - EXPECT_EQ(0U, workspace.revision()); - - const auto committed = workspace.commitWorkflowCandidateJar(request, expected(0)); - ASSERT_TRUE(committed.mutation.status.is_ok()); - EXPECT_TRUE(committed.mutation.committed); - EXPECT_EQ(1U, committed.mutation.revision); - EXPECT_TRUE(committed.exported); - ASSERT_TRUE(std::filesystem::exists(exportedJar)); - EXPECT_TRUE(workspace.classInfo("workspace/Subject").is_err()); - ASSERT_TRUE(workspace.classInfo("workspace/RenamedSubject").is_ok()); - - WorkspaceBase exported; - ASSERT_TRUE(exported.openJar(exportedJar.string()).is_ok()); - ASSERT_TRUE(exported.classInfo("workspace/RenamedSubject").is_ok()); - - const auto staleExport = - test::getTempOutPath("mjbase_workspace_stale_candidate_export.jar"); - std::filesystem::remove(staleExport, filesystemError); - auto staleRequest = request; - staleRequest.exportPath = staleExport.string(); - const auto stale = workspace.commitWorkflowCandidateJar(staleRequest, expected(0)); - EXPECT_EQ(WorkspaceError::Aborted, stale.mutation.status.code); - EXPECT_FALSE(stale.exported); - EXPECT_FALSE(std::filesystem::exists(staleExport)); -} - -// ── JASM 变更事务 ────────────────────────────────────────── - -TEST(MjWorkspaceCoreTest, AppliesValidatedJasmWithRevisionAndHistory) -{ - WorkspaceBase workspace; - const auto input = writeWorkspaceFixture(); - ASSERT_TRUE(workspace.openJar(input.string()).is_ok()); - auto sourceName = utf16("Subject.jasm"); - auto replacement = utf16(classSource("workspace/Subject", "42")); - - auto stale = workspace.applyJasm("workspace/Subject.class", sourceName.ref(), - replacement.ref(), expected(1)); - EXPECT_EQ(WorkspaceError::Aborted, stale.status.code); - EXPECT_EQ(0U, stale.revision); - - auto preview = workspace.validateJasm("workspace/Subject.class", sourceName.ref(), - replacement.ref(), expected(0, true)); - EXPECT_TRUE(preview.status.is_ok()); - EXPECT_FALSE(preview.committed); - EXPECT_EQ(0U, preview.revision); - ASSERT_FALSE(preview.byteDiffs.empty()); - EXPECT_TRUE(preview.exportable); - - ASSERT_TRUE(workspace.createSavepoint("original").is_ok()); - auto applied = workspace.applyJasm("workspace/Subject.class", sourceName.ref(), - replacement.ref(), expected(0)); - ASSERT_TRUE(applied.status.is_ok()); - EXPECT_TRUE(applied.committed); - EXPECT_EQ(1U, applied.revision); - EXPECT_TRUE(workspace.isDirty()); - ASSERT_EQ(1U, applied.affectedClasses.size()); - EXPECT_EQ("workspace/Subject", applied.affectedClasses[0]); - - auto undone = workspace.undo(expected(1)); - ASSERT_TRUE(undone.status.is_ok()); - EXPECT_EQ(2U, undone.revision); - EXPECT_FALSE(workspace.isDirty()); - - auto redone = workspace.redo(expected(2)); - ASSERT_TRUE(redone.status.is_ok()); - EXPECT_EQ(3U, redone.revision); - EXPECT_TRUE(workspace.isDirty()); - - auto restored = workspace.restoreSavepoint("original", expected(3)); - ASSERT_TRUE(restored.status.is_ok()); - EXPECT_EQ(4U, restored.revision); - EXPECT_FALSE(workspace.isDirty()); -} - -// ── 结构化 class 变更(RemoveInstruction 负例改序号越界)──── - -TEST(MjWorkspaceCoreTest, AppliesStructuredClassMutationsThroughOneTransactionPath) -{ - WorkspaceBase workspace; - const auto input = writeWorkspaceFixture(); - ASSERT_TRUE(workspace.openJar(input.string()).is_ok()); - - ClassMutation setFieldAccess; - setFieldAccess.classUri = "workspace/Subject.class"; - setFieldAccess.kind = ClassMutationKind::SetFieldAccess; - setFieldAccess.member = {"counter", "I"}; - setFieldAccess.access = kAccPrivate; - - auto preview = workspace.validateClassMutation(setFieldAccess, expected(0, true)); - ASSERT_TRUE(preview.status.is_ok()); - EXPECT_FALSE(preview.committed); - EXPECT_EQ(0U, workspace.revision()); - ASSERT_FALSE(preview.byteDiffs.empty()); - - auto accessChanged = workspace.applyClassMutation(setFieldAccess, expected(0)); - ASSERT_TRUE(accessChanged.status.is_ok()); - EXPECT_TRUE(accessChanged.committed); - EXPECT_EQ(1U, accessChanged.revision); - auto field = workspace.fieldInfo("workspace/Subject", "counter", "I"); - ASSERT_TRUE(field.is_ok()); - EXPECT_EQ(static_cast(kAccPrivate), field.unwrap().access); - - // 代码指令序号口径:value()I 体 = [ldc 1, ireturn],0 号即 ldc。 - ClassMutation replaceInstruction; - replaceInstruction.classUri = "workspace/Subject.class"; - replaceInstruction.kind = ClassMutationKind::ReplaceInstruction; - replaceInstruction.member = {"value", "()I"}; - replaceInstruction.instructionIndex = 0; - replaceInstruction.instruction.kind = InstructionKind::LdcInt; - replaceInstruction.instruction.operand = 42; - auto instructionChanged = workspace.applyClassMutation(replaceInstruction, expected(1)); - ASSERT_TRUE(instructionChanged.status.is_ok()); - EXPECT_EQ(2U, instructionChanged.revision); - - ClassMutation addMethod; - addMethod.classUri = "workspace/Subject.class"; - addMethod.kind = ClassMutationKind::AddMethod; - addMethod.member = {"added", "()V"}; - addMethod.access = kAccPublic | kAccStatic; - addMethod.initialInstructions.push_back({InstructionKind::Simple, 0xb1 /* RETURN */}); - auto methodAdded = workspace.applyClassMutation(addMethod, expected(2)); - ASSERT_TRUE(methodAdded.status.is_ok()); - EXPECT_EQ(3U, methodAdded.revision); - EXPECT_TRUE(workspace.methodInfo("workspace/Subject", "added", "()V").is_ok()); - - ClassMutation removeMethod; - removeMethod.classUri = "workspace/Subject.class"; - removeMethod.kind = ClassMutationKind::RemoveMethod; - removeMethod.member = {"added", "()V"}; - auto methodRemoved = workspace.applyClassMutation(removeMethod, expected(3)); - ASSERT_TRUE(methodRemoved.status.is_ok()); - EXPECT_EQ(4U, methodRemoved.revision); - EXPECT_EQ(WorkspaceError::NotFound, - workspace.methodInfo("workspace/Subject", "added", "()V").unwrap_err().code); - - ClassMutation addMarker; - addMarker.classUri = "workspace/Subject.class"; - addMarker.kind = ClassMutationKind::AddMarkerAnnotation; - addMarker.annotationTarget = AnnotationTarget::Field; - addMarker.annotationVisible = true; - addMarker.annotationDescriptor = "Lworkspace/Marker;"; - addMarker.member = {"counter", "I"}; - auto markerAdded = workspace.applyClassMutation(addMarker, expected(4)); - ASSERT_TRUE(markerAdded.status.is_ok()); - EXPECT_EQ(5U, markerAdded.revision); - auto annotatedField = workspace.fieldInfo("workspace/Subject", "counter", "I"); - ASSERT_TRUE(annotatedField.is_ok()); - ASSERT_EQ(1U, annotatedField.unwrap().annotations.size()); - EXPECT_EQ("Lworkspace/Marker;", annotatedField.unwrap().annotations[0].descriptor); - - // 负例改写(类型面):树序号 0 命中 label 节点被拒;代码指令 - // 序号 0 是未引用 ldc、移除会成功——拒收面改断言序号越界(同一错误通道)。 - ClassMutation invalidDelete; - invalidDelete.classUri = "workspace/Subject.class"; - invalidDelete.kind = ClassMutationKind::RemoveInstruction; - invalidDelete.member = {"value", "()I"}; - invalidDelete.instructionIndex = 99; - auto rejected = workspace.applyClassMutation(invalidDelete, expected(5)); - EXPECT_FALSE(rejected.status.is_ok()); - EXPECT_EQ(WorkspaceError::InvalidArgument, rejected.status.code); - EXPECT_EQ(5U, workspace.revision()); -} - -// ── 原始字节替换 / revert ───────────────────────────────── - -TEST(MjWorkspaceCoreTest, ReplacesListsAndRevertsRawClassBytesTransactionally) -{ - WorkspaceBase workspace; - ASSERT_TRUE(workspace.openJar(writeWorkspaceFixture().string()).is_ok()); - const auto replacement = assemble("workspace/Subject", "42"); - - auto preview = workspace.replaceClassBytes("workspace/Subject.class", replacement, - expected(0, true)); - ASSERT_TRUE(preview.status.is_ok()); - EXPECT_FALSE(preview.committed); - EXPECT_EQ(0U, workspace.revision()); - ASSERT_EQ(1U, preview.byteDiffs.size()); - ASSERT_TRUE(workspace.modifiedClasses().is_ok()); - EXPECT_TRUE(workspace.modifiedClasses().unwrap().empty()); - - auto badMagic = replacement; - badMagic[0] = 0; - EXPECT_EQ(WorkspaceError::InvalidArgument, - workspace.replaceClassBytes("workspace/Subject.class", badMagic, expected(0)) - .status.code); - EXPECT_EQ(WorkspaceError::InvalidArgument, - workspace.replaceClassBytes("workspace/Subject.class", - assemble("workspace/Other", "1"), expected(0)) - .status.code); - - auto applied = workspace.replaceClassBytes("workspace/Subject", replacement, expected(0)); - ASSERT_TRUE(applied.status.is_ok()); - EXPECT_TRUE(applied.committed); - EXPECT_EQ(1U, workspace.revision()); - auto modified = workspace.modifiedClasses(); - ASSERT_TRUE(modified.is_ok()); - ASSERT_EQ(1U, modified.unwrap().size()); - EXPECT_EQ("workspace/Subject.class", modified.unwrap()[0].resourceUri); - EXPECT_NE(modified.unwrap()[0].originalFingerprint, modified.unwrap()[0].currentFingerprint); - - auto revertPreview = workspace.revertClass("workspace/Subject.class", expected(1, true)); - ASSERT_TRUE(revertPreview.status.is_ok()); - EXPECT_FALSE(revertPreview.committed); - EXPECT_EQ(1U, workspace.revision()); - EXPECT_TRUE(workspace.isDirty()); - - auto reverted = workspace.revertClass("workspace/Subject.class", expected(1)); - ASSERT_TRUE(reverted.status.is_ok()); - EXPECT_TRUE(reverted.committed); - EXPECT_EQ(2U, workspace.revision()); - EXPECT_FALSE(workspace.isDirty()); - ASSERT_TRUE(workspace.modifiedClasses().is_ok()); - EXPECT_TRUE(workspace.modifiedClasses().unwrap().empty()); - - ASSERT_TRUE(workspace.undo(expected(2)).status.is_ok()); - EXPECT_TRUE(workspace.isDirty()); - ASSERT_TRUE(workspace.redo(expected(3)).status.is_ok()); - EXPECT_FALSE(workspace.isDirty()); -} - -// ── 指令族构建(test 12,等价改写:代码序号 + MH/Dynamic 拒收面)───────── - -TEST(MjWorkspaceCoreTest, BuildsExecutableInstructionFamiliesFromSymbolicLabels) -{ - WorkspaceBase workspace; - ASSERT_TRUE(workspace.openJar(writeWorkspaceFixture().string()).is_ok()); - ca::u64 revision = 0; - - ClassMutation field; - field.classUri = "workspace/Subject.class"; - field.kind = ClassMutationKind::AddField; - field.member = {"answer", "I"}; - field.access = kAccPublic | kAccStatic | kAccFinal; - field.hasFieldValue = true; - field.fieldValue.kind = ConstantKind::Integer; - field.fieldValue.intValue = 42; - auto fieldAdded = workspace.applyClassMutation(field, expected(revision)); - ASSERT_TRUE(fieldAdded.status.is_ok()) << fieldAdded.status.message; - ++revision; - - ClassMutation genericField; - genericField.classUri = "workspace/Subject.class"; - genericField.kind = ClassMutationKind::AddField; - genericField.member = {"names", "Ljava/util/List;"}; - genericField.access = kAccPrivate; - genericField.signature = "Ljava/util/List;"; - ASSERT_TRUE(workspace.applyClassMutation(genericField, expected(revision++)).status.is_ok()); - auto names = workspace.fieldInfo("workspace/Subject", "names", "Ljava/util/List;"); - ASSERT_TRUE(names.is_ok()); - EXPECT_EQ(genericField.signature, names.unwrap().signature); - - ClassMutation invalidAccess = field; - invalidAccess.member = {"invalid", "I"}; - invalidAccess.access = kAccFinal | kAccVolatile; - auto rejected = workspace.applyClassMutation(invalidAccess, expected(revision)); - EXPECT_EQ(WorkspaceError::InvalidArgument, rejected.status.code); - EXPECT_EQ(revision, workspace.revision()); - - const auto simple = [](ca::i32 opcode) { - InstructionSpec value; - value.kind = InstructionKind::Simple; - value.opcode = opcode; - return value; - }; - const auto var = [](ca::i32 opcode, ca::i32 slot) { - InstructionSpec value; - value.kind = InstructionKind::Var; - value.opcode = opcode; - value.operand = slot; - return value; - }; - const auto label = [](const std::string& id) { - InstructionSpec value; - value.kind = InstructionKind::Label; - value.label = id; - return value; - }; - const auto ldcInt = [](ca::i32 number) { - InstructionSpec value; - value.kind = InstructionKind::LdcInt; - value.operand = number; - return value; - }; - const auto addMethod = [&](ClassMutation mutation) { - mutation.classUri = "workspace/Subject.class"; - mutation.kind = ClassMutationKind::AddMethod; - mutation.access = kAccPublic | kAccStatic; - auto result = workspace.applyClassMutation(mutation, expected(revision)); - if (result.status.is_ok()) ++revision; - return result; - }; - - ClassMutation branch; - branch.member = {"branch", "(I)I"}; - branch.signature = "(I)I"; - branch.exceptions = {"java/lang/RuntimeException"}; - InstructionSpec jump; - jump.kind = InstructionKind::Jump; - jump.opcode = 0x99 /* IFEQ */; - jump.label = "zero"; - branch.initialInstructions = {label("start"), var(0x15 /* ILOAD */, 0), jump, ldcInt(1), - simple(0xac /* IRETURN */), label("zero"), ldcInt(0), - simple(0xac), label("end"), label("handler"), - simple(0x57 /* POP */), ldcInt(-1), simple(0xac)}; - TryCatchSpec catchBlock; - catchBlock.type = "java/lang/RuntimeException"; - catchBlock.startLabel = "start"; - catchBlock.endLabel = "end"; - catchBlock.handlerLabel = "handler"; - branch.tryCatchBlocks.push_back(catchBlock); - branch.localVariables.push_back({"value", "I", {}, "start", "end", 0}); - const auto branchResult = addMethod(branch); - ASSERT_TRUE(branchResult.status.is_ok()) << branchResult.status.message; - - // 代码指令序号口径:branch 体序号 = [iload 0, ifeq, ldc 1, ireturn, ldc 0, - // ireturn, pop, ldc -1, ireturn]——ifeq 为 1,ldc 0 为 4 - //("zero" 绑定点)。 - ClassMutation replaceJump; - replaceJump.classUri = "workspace/Subject.class"; - replaceJump.kind = ClassMutationKind::ReplaceInstruction; - replaceJump.member = branch.member; - replaceJump.instructionIndex = 1; - replaceJump.existingLabels.push_back({"zero", 4}); - replaceJump.instruction.kind = InstructionKind::Jump; - replaceJump.instruction.opcode = 0x9a /* IFNE */; - replaceJump.instruction.label = "zero"; - auto jumpChanged = workspace.applyClassMutation(replaceJump, expected(revision)); - ASSERT_TRUE(jumpChanged.status.is_ok()) << jumpChanged.status.message; - ++revision; - - ClassMutation table; - table.member = {"table", "(I)I"}; - InstructionSpec tableSwitch; - tableSwitch.kind = InstructionKind::TableSwitch; - tableSwitch.minimum = 0; - tableSwitch.maximum = 1; - tableSwitch.defaultLabel = "default"; - tableSwitch.labels = {"case0", "case1"}; - table.initialInstructions = {var(0x15, 0), tableSwitch, label("case0"), ldcInt(10), - simple(0xac), label("case1"), ldcInt(11), simple(0xac), - label("default"), ldcInt(-1), simple(0xac)}; - ASSERT_TRUE(addMethod(table).status.is_ok()); - - ClassMutation lookup; - lookup.member = {"lookup", "(I)I"}; - InstructionSpec lookupSwitch; - lookupSwitch.kind = InstructionKind::LookupSwitch; - lookupSwitch.defaultLabel = "default"; - lookupSwitch.keys = {7, 42}; - lookupSwitch.labels = {"seven", "fortyTwo"}; - lookup.initialInstructions = {var(0x15, 0), lookupSwitch, label("seven"), ldcInt(7), - simple(0xac), label("fortyTwo"), ldcInt(42), simple(0xac), - label("default"), ldcInt(-1), simple(0xac)}; - ASSERT_TRUE(addMethod(lookup).status.is_ok()); - - ClassMutation multiArray; - multiArray.member = {"matrix", "()[[I"}; - InstructionSpec array; - array.kind = InstructionKind::MultiANewArray; - array.descriptor = "[[I"; - array.dimensions = 2; - multiArray.initialInstructions = {ldcInt(2), ldcInt(3), array, simple(0xb0 /* ARETURN */)}; - ASSERT_TRUE(addMethod(multiArray).status.is_ok()); - - // 宽常量族:LdcLong/LdcFloat/LdcDouble/LdcType(mjbase InstructionSpec 契约: - // LdcType 收 JVM 内部名)。LdcHandle/LdcDynamic 家族不进成功面——libjbc - // 常量池无 MethodHandle/Dynamic 追加 API(主线裁决点,见文件头注)。 - ClassMutation constants; - constants.member = {"constants", "()V"}; - InstructionSpec wideLong; - wideLong.kind = InstructionKind::LdcLong; - wideLong.longValue = 0x102030405060708LL; - InstructionSpec wideFloat; - wideFloat.kind = InstructionKind::LdcFloat; - wideFloat.floatValue = 1.25F; - InstructionSpec wideDouble; - wideDouble.kind = InstructionKind::LdcDouble; - wideDouble.doubleValue = 2.5; - InstructionSpec type; - type.kind = InstructionKind::LdcType; - type.descriptor = "java/lang/String"; - constants.initialInstructions = {wideLong, simple(0x58 /* POP2 */), wideFloat, - simple(0x57 /* POP */), wideDouble, simple(0x58), type, - simple(0x57), simple(0xb1 /* RETURN */)}; - ASSERT_TRUE(addMethod(constants).status.is_ok()); - - // indy 拒收钉子:AddMethod(InvokeDynamic) 成功;入口即拒 - //(InvalidArgument),产物不出半成品。 - ClassMutation indy; - indy.member = {"concat", "(I)Ljava/lang/String;"}; - InstructionSpec invokeDynamic; - invokeDynamic.kind = InstructionKind::InvokeDynamic; - invokeDynamic.name = "makeConcatWithConstants"; - invokeDynamic.descriptor = "(I)Ljava/lang/String;"; - invokeDynamic.bootstrapMethod.tag = 6 /* H_INVOKESTATIC */; - invokeDynamic.bootstrapMethod.owner = "java/lang/invoke/StringConcatFactory"; - invokeDynamic.bootstrapMethod.name = "makeConcatWithConstants"; - invokeDynamic.bootstrapMethod.descriptor = - "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/" - "MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;"; - ConstantSpec recipe; - recipe.kind = ConstantKind::String; - recipe.stringValue = "value=\1"; - invokeDynamic.bootstrapArguments.push_back(recipe); - indy.initialInstructions = {var(0x15, 0), invokeDynamic, simple(0xb0)}; - auto indyRejected = addMethod(indy); - EXPECT_FALSE(indyRejected.status.is_ok()); - EXPECT_EQ(WorkspaceError::InvalidArgument, indyRejected.status.code); - - ClassMutation replacement = branch; - replacement.classUri = "workspace/Subject.class"; - replacement.kind = ClassMutationKind::ReplaceMethodBody; - replacement.member = {"value", "()I"}; - replacement.signature.clear(); - replacement.exceptions.clear(); - replacement.tryCatchBlocks.clear(); - replacement.localVariables.clear(); - replacement.initialInstructions = {ldcInt(99), simple(0xac)}; - ASSERT_TRUE(workspace.applyClassMutation(replacement, expected(revision++)).status.is_ok()); - - auto bytes = workspace.readClassBytes("workspace/Subject"); - ASSERT_TRUE(bytes.is_ok()); - jasm::JvmJasmService validator; - EXPECT_TRUE(validator.validate(bytes.unwrap().bytes).isOk()); - auto constantsInfo = workspace.methodInfo("workspace/Subject", "constants", "()V"); - ASSERT_TRUE(constantsInfo.is_ok()) << constantsInfo.unwrap_err().message; - EXPECT_EQ(revision, workspace.revision()); -} - -// ── diff + 修复事务 ─────────────────────────────────────── - -TEST(MjWorkspaceCoreTest, DiffsAllResourcesAndRepairsSelectedJarDamageTransactionally) -{ - WorkspaceBase workspace; - ASSERT_TRUE(workspace.openJar(writeWorkspaceFixture().string()).is_ok()); - - model::ResourceContainer reference; - reference.addResource("workspace/Subject.class", - std::make_unique( - "workspace/Subject.class", assemble("workspace/Subject", "2"))); - reference.addResource("data.txt", - std::make_unique( - "data.txt", - std::vector{'c', 'h', 'a', 'n', 'g', 'e', 'd', 'x', 'x'})); - reference.addResource("reference-only.txt", - std::make_unique( - "reference-only.txt", std::vector{'n', 'e', 'w'})); - const auto referencePath = test::getTempOutPath("mjbase_workspace_diff_reference.jar"); - model::ResourceHelper::exportToJar(reference, referencePath.string()); - - const auto diff = workspace.diffJar(referencePath.string()); - ASSERT_TRUE(diff.status.is_ok()); - EXPECT_GE(diff.workspaceOnly, 3U); - EXPECT_GE(diff.referenceOnly, 1U); - EXPECT_GE(diff.modified, 2U); - EXPECT_EQ(diff.workspaceOnly + diff.referenceOnly + diff.modified, diff.byteDiffs.size()); - EXPECT_NE(std::find_if(diff.byteDiffs.begin(), diff.byteDiffs.end(), - [](const ByteDiff& item) { - return item.uri == "data.txt" && item.beforeSize == item.afterSize; - }), - diff.byteDiffs.end()); - EXPECT_NE(std::find_if(diff.byteDiffs.begin(), diff.byteDiffs.end(), - [](const ByteDiff& item) { - return item.uri == "reference-only.txt" && item.beforeSize == 0; - }), - diff.byteDiffs.end()); - - WorkspaceBase repairWorkspace; - ASSERT_TRUE(repairWorkspace.openJar(writeRepairFixture().string()).is_ok()); - const auto inspected = repairWorkspace.analyzeJarRepair({}, expected(0, true)); - ASSERT_TRUE(inspected.mutation.status.is_ok()); - EXPECT_GE(inspected.resourceCount, 3U); - EXPECT_EQ(2U, inspected.classCount); - ASSERT_EQ(3U, inspected.issues.size()); - EXPECT_FALSE(inspected.issues[0].selectedForRemoval); - EXPECT_TRUE(inspected.mutation.byteDiffs.empty()); - - // P18a:验证门只覆盖操作触碰的类——只摘 marker 的部分修复不再被未触碰 - // 的 broken/Truncated.class 卡死(旧整体门语义废除);未触碰的坏类原样 - // 留存,修复与否由后续修复请求决定。 - JarRepairRequest partialRepair; - partialRepair.removeCorruptOutputMarkers = true; - const auto partial = - repairWorkspace.applyJarRepair(partialRepair, expected(0)); - ASSERT_TRUE(partial.mutation.status.is_ok()); - EXPECT_TRUE(partial.mutation.committed); - EXPECT_EQ(1U, partial.mutation.revision); - EXPECT_FALSE(repairWorkspace.readResource("logs/corrupt_output_stream.txt").is_ok()); - EXPECT_TRUE(repairWorkspace.readResource("broken/Truncated.class").is_ok()); - - JarRepairRequest repair; - repair.removeInvalidClassEntries = true; - repair.removeCorruptOutputMarkers = true; - const auto preview = repairWorkspace.analyzeJarRepair(repair, expected(1, true)); - ASSERT_TRUE(preview.mutation.status.is_ok()); - EXPECT_FALSE(preview.mutation.committed); - ASSERT_EQ(2U, preview.mutation.byteDiffs.size()); - EXPECT_TRUE(std::all_of(preview.issues.begin(), preview.issues.end(), - [](const JarRepairIssue& issue) { - return issue.selectedForRemoval; - })); - - const auto applied = repairWorkspace.applyJarRepair(repair, expected(1)); - ASSERT_TRUE(applied.mutation.status.is_ok()); - EXPECT_TRUE(applied.mutation.committed); - EXPECT_EQ(2U, applied.mutation.revision); - auto resources = repairWorkspace.listResources(); - ASSERT_TRUE(resources.is_ok()); - auto resourceList = std::move(resources).unwrap(); - EXPECT_EQ(std::find_if(resourceList.begin(), resourceList.end(), - [](const ResourceSummary& item) { - return item.uri == "broken/Invalid.class"; - }), - resourceList.end()); - EXPECT_TRUE(repairWorkspace.readResource("workspace/RepairSubject.class").is_ok()); - - const auto restored = repairWorkspace.undo(expected(2)); - ASSERT_TRUE(restored.status.is_ok()); - EXPECT_EQ(3U, restored.revision); - EXPECT_TRUE(repairWorkspace.readResource("broken/Invalid.class").is_ok()); -} - -TEST(MjWorkspaceCoreTest, RejectsInvalidJasmAndProtectsInputDuringAtomicExport) -{ - WorkspaceBase workspace; - const auto input = writeWorkspaceFixture(); - ASSERT_TRUE(workspace.openJar(input.string()).is_ok()); - auto sourceName = utf16("Subject.jasm"); - auto invalid = utf16(classSource("workspace/Subject", "1\n return")); - - auto rejected = - workspace.applyJasm("workspace/Subject.class", sourceName.ref(), invalid.ref(), expected(0)); - EXPECT_EQ(WorkspaceError::InvalidArgument, rejected.status.code); - EXPECT_EQ(0U, rejected.revision); - EXPECT_FALSE(workspace.isDirty()); - ASSERT_FALSE(rejected.diagnostics.empty()); - - EXPECT_EQ(WorkspaceError::PermissionDenied, - workspace.exportJar(input.string()).unwrap_err().code); - const auto output = test::getTempOutPath("mjbase_workspace_core_output.jar"); - std::error_code filesystemError; - std::filesystem::remove(output, filesystemError); - ASSERT_TRUE(workspace.exportJar(output.string()).is_ok()); - EXPECT_TRUE(std::filesystem::exists(output)); - - auto reverted = workspace.revertClass("workspace/Subject.class", expected(0)); - ASSERT_TRUE(reverted.status.is_ok()); - EXPECT_FALSE(reverted.committed); - EXPECT_EQ(0U, reverted.revision); -} - -// ── 接口多态 ────────────────────────────────────────────── - -TEST(MjWorkspaceCoreTest, UsesIWorkspaceThroughInterfacePolymorphism) -{ - std::unique_ptr workspace = std::make_unique(); - ASSERT_TRUE(workspace->openJar(writeWorkspaceFixture().string()).is_ok()); - - EXPECT_TRUE(workspace->isLoaded()); - EXPECT_EQ(ArtifactKind::Jar, workspace->artifactKind()); - - auto summaries = workspace->listClassSummaries(); - ASSERT_TRUE(summaries.is_ok()); - // unwrap() 按值返回:先物化再取迭代器,避免两次 unwrap() 产生不同临时 vector - // 导致跨容器迭代器比较(MSVC debug 断言,release 下为 UB)。 - const auto summaryList = summaries.unwrap(); - EXPECT_NE(std::find_if(summaryList.begin(), summaryList.end(), - [](const ClassSummary& s) { - return s.internalName == "workspace/Subject"; - }), - summaryList.end()); - - auto classes = workspace->searchClasses("subject"); - ASSERT_TRUE(classes.is_ok()); - EXPECT_FALSE(classes.unwrap().empty()); - - // 变更路径同样经接口:applyClassMutation 事务语义不变。 - ClassMutation mutation; - mutation.classUri = "workspace/Subject.class"; - mutation.kind = ClassMutationKind::AddField; - mutation.member.name = "extra"; - mutation.member.descriptor = "I"; - mutation.access = kAccPublic; - auto applied = workspace->applyClassMutation(mutation); - ASSERT_TRUE(applied.status.is_ok()); - EXPECT_TRUE(applied.committed); - EXPECT_EQ(1U, applied.revision); - - auto classes2 = workspace->listClassSummaries(); - ASSERT_TRUE(classes2.is_ok()); - // 同上:物化一次,避免 find_if 返回的迭代器指向已析构的临时 vector。 - const auto classList = classes2.unwrap(); - auto subject = std::find_if(classList.begin(), classList.end(), - [](const ClassSummary& s) { - return s.internalName == "workspace/Subject"; - }); - ASSERT_NE(subject, classList.end()); - EXPECT_EQ(2U, subject->fieldCount); // 原 counter + 新增 extra -} - -// ── Local 直通(旧 RoutesLocalRenameThroughClassMutationDirectly 等价改写)── - -TEST(MjWorkspaceCoreTest, RenamesLocalsThroughClassMutationTransaction) -{ - WorkspaceBase workspace; - ASSERT_TRUE(workspace.openJar(writeLocalHolderFixture().string()).is_ok()); - - // rename 编排面(analyzeRename/applyRename)未随迁;Local 能力由 - // ClassMutationKind::RenameLocal 承载,断言 LVT 记录名 held→kept、 - // index/descriptor 不变(class_mutation_service 直改 LVT/LVTT 行)。 - ClassMutation rename; - rename.classUri = "workspace/LocalHolder.class"; - rename.kind = ClassMutationKind::RenameLocal; - rename.member = {"hold", "()I"}; - rename.localName = "held"; - rename.replacementLocalName = "kept"; - auto applied = workspace.applyClassMutation(rename, expected(0)); - ASSERT_TRUE(applied.status.is_ok()) << applied.status.message; - EXPECT_TRUE(applied.committed); - EXPECT_EQ(1U, applied.revision); - - auto bytes = workspace.readClassBytes("workspace/LocalHolder"); - ASSERT_TRUE(bytes.is_ok()); - const auto holder = std::move(bytes).unwrap(); // 物化一次,避免 unwrap() 临时悬挂 - auto read = jbc::core::readClass(jbc::core::ByteView(holder.bytes)); - ASSERT_TRUE(read.is_ok()); - auto parsed = std::move(read).unwrap(); - jbc::core::ClassFile& cf = *parsed.file; - jbc::core::MemberInfo* hold = nullptr; - for (auto& method : cf.methods) { - if (support::utf8Text(cf, method.nameCp) == "hold" && - support::utf8Text(cf, method.descCp) == "()I") { - hold = &method; - break; - } - } - ASSERT_TRUE(hold != nullptr); - jbc::core::CodeAttr* code = support::ensureCode(cf, *hold); - ASSERT_TRUE(code != nullptr); - const jbc::core::LocalVariableTableAttr* lvt = nullptr; - for (jbc::core::AttrSlot& nested : code->attrs) { - if (!cf.cp.utf8Equals(nested.nameCp, "LocalVariableTable")) continue; - if (!nested.decoded() && cf.decodeAttr(nested).is_err()) continue; - lvt = ca::core::tag_cast::cast(nested.peek()); - break; - } - ASSERT_TRUE(lvt != nullptr); - ASSERT_EQ(1U, lvt->rows.size()); - EXPECT_EQ("kept", support::utf8Text(cf, lvt->rows[0].nameCp)); - EXPECT_EQ("I", support::utf8Text(cf, lvt->rows[0].descCp)); - EXPECT_EQ(0U, lvt->rows[0].slot); - - // 负例(今日事实行为):owner 传不存在的方法名 → NOT_FOUND、revision 不动。 - ClassMutation missing; - missing.classUri = "workspace/LocalHolder.class"; - missing.kind = ClassMutationKind::RenameLocal; - missing.member = {"nope", "()I"}; - missing.localName = "held"; - missing.replacementLocalName = "whatever"; - auto notFound = workspace.applyClassMutation(missing, expected(1)); - EXPECT_EQ(WorkspaceError::NotFound, notFound.status.code); - EXPECT_EQ(1U, workspace.revision()); -} - -// ── 覆盖组件报告(旧 ReportsOverrideComponentWithPrimaryAndSupportingDecoration -// 组件游走已等价重建(mjbase 层次图并查集闭包,不经 renamer), -// 断言逐字保留:owner 字典序、primary 只落请求点名的声明、支撑库成员带 -// supporting/supportId 装饰、owner 未登记与 owner 在但方法未声明双 NOT_FOUND。 -TEST(MjWorkspaceCoreTest, ReportsOverrideComponentWithPrimaryAndSupportingDecoration) -{ - WorkspaceBase workspace; - ASSERT_TRUE(workspace.openJar(writeWorkspaceFixture().string()).is_ok()); - ASSERT_TRUE(workspace.addSupportingJar(writeSupportingFixture().string()).is_ok()); - - auto overrides = workspace.overridesOfMethod("workspace/Subject", "virtualValue", "()I"); - ASSERT_TRUE(overrides.is_ok()); - const auto members = std::move(overrides).unwrap(); - ASSERT_EQ(3U, members.size()); - EXPECT_EQ("support/Library", members[0].owner); - EXPECT_FALSE(members[0].primary); - EXPECT_TRUE(members[0].supporting); - EXPECT_EQ("support-1", members[0].supportId); - EXPECT_EQ("workspace/Child", members[1].owner); - EXPECT_FALSE(members[1].primary); - EXPECT_FALSE(members[1].supporting); - EXPECT_TRUE(members[1].supportId.empty()); - EXPECT_EQ("workspace/Subject", members[2].owner); - EXPECT_TRUE(members[2].primary); - EXPECT_FALSE(members[2].supporting); - for (const auto& member : members) { - EXPECT_EQ("virtualValue", member.name); - EXPECT_EQ("()I", member.descriptor); - } - - // owner 不在工作区/支撑库 → NOT_FOUND;owner 在但方法未声明 → NOT_FOUND。 - auto missingOwner = workspace.overridesOfMethod("missing/Type", "virtualValue", "()I"); - ASSERT_TRUE(missingOwner.is_err()); - EXPECT_EQ(WorkspaceError::NotFound, missingOwner.unwrap_err().code); - auto missingMethod = workspace.overridesOfMethod("workspace/Subject", "nope", "()I"); - ASSERT_TRUE(missingMethod.is_err()); - EXPECT_EQ(WorkspaceError::NotFound, missingMethod.unwrap_err().code); -} - -// ── summary 扫描单后端字段契约 ─────── -// -// 单底座对拍面收敛为单后端字段契约:对同一 (uri, bytes) 断言 internalName/ -// superName/access/version/interfaceCount/fieldCount/methodCount(resourceUri -// 原样回填),强度与双后端逐字段相等断言一致。 - -namespace { - -std::string plainSummarySource(const std::string& name) -{ - return ".super java/lang/Object\n" - ".class public super " + - name + - " {\n" - " .field public counter I\n" - " .method public static value ()I {\n" - " code: {\n" - " bipush 7\n" - " ireturn\n" - " }\n" - " }\n" - "}\n"; -} - -std::string richSummarySource(const std::string& name) -{ - // 注:不声明 ——jhasm 方法名不支持尖括号(现有夹具亦无构造器声明), - // 契约关注接口/字段/方法计数,构造器不在覆盖面内。 - return ".super java/lang/Object\n" - ".implements java/lang/Runnable\n" - ".implements java/io/Serializable\n" - ".class public super " + - name + - " {\n" - " .field private id J\n" - " .field public label Ljava/lang/String;\n" - " .method public run ()V {\n" - " code: {\n" - " return\n" - " }\n" - " }\n" - " .method public static compute (II)I {\n" - " code: {\n" - " iload 0\n" - " iload 1\n" - " iadd\n" - " ireturn\n" - " }\n" - " }\n" - "}\n"; -} - -} // namespace - -TEST(MjWorkspaceSummaryTest, FieldContractOnPlainClass) -{ - const auto bytes = assembleSource("parity/Plain", plainSummarySource("parity/Plain")); - model::ClassResource resource("parity/Plain.class", bytes); - auto decoded = decodeClassSummary("parity/Plain.class", resource); - ASSERT_TRUE(decoded.is_ok()); - const ClassSummary summary = std::move(decoded).unwrap(); - EXPECT_EQ("parity/Plain.class", summary.resourceUri); - EXPECT_EQ("parity/Plain", summary.internalName); - EXPECT_EQ("java/lang/Object", summary.superName); - EXPECT_EQ(kAccPublic | kAccSuper, static_cast(summary.access)); - EXPECT_EQ(0U, summary.interfaceCount); - EXPECT_EQ(1U, summary.fieldCount); - EXPECT_EQ(1U, summary.methodCount); -} - -TEST(MjWorkspaceSummaryTest, FieldContractOnRichClass) -{ - const auto bytes = assembleSource("parity/Rich", richSummarySource("parity/Rich")); - model::ClassResource resource("parity/Rich.class", bytes); - auto decoded = decodeClassSummary("parity/Rich.class", resource); - ASSERT_TRUE(decoded.is_ok()); - const ClassSummary summary = std::move(decoded).unwrap(); - EXPECT_EQ("parity/Rich.class", summary.resourceUri); - EXPECT_EQ("parity/Rich", summary.internalName); - EXPECT_EQ("java/lang/Object", summary.superName); - EXPECT_EQ(static_cast(kAccPublic | kAccSuper), - static_cast(summary.access) & (kAccPublic | kAccSuper | kAccInterface)); - EXPECT_EQ(2U, summary.interfaceCount); - EXPECT_EQ(2U, summary.fieldCount); - EXPECT_EQ(2U, summary.methodCount); -} - -TEST(MjWorkspaceSummaryTest, RejectsGarbage) -{ - const std::vector garbage{0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0x00, 0xFF, 0xFF, 0x00}; - model::ClassResource resource("parity/Garbage.class", garbage); - auto decoded = decodeClassSummary("parity/Garbage.class", resource); - ASSERT_TRUE(decoded.is_err()); - EXPECT_EQ(WorkspaceError::DataLoss, decoded.unwrap_err().code); -} - - - -// ── Multi-Release 族:内名选择器确定序 + 单条 URI 替换语义 ───────────────── -// -// 内名命中 base + versions/ 变体并存的族时,选择器按确定序取 base(不依赖 -// 容器迭代序);replaceClassBytes 是单条 URI 语义——替换只落选定条目,族内 -// 其它条目字节不动。 - -TEST(MjWorkspaceCoreTest, ResolvesAndReplacesBaseEntryOfMultiReleaseFamily) -{ - WorkspaceBase workspace; - ASSERT_TRUE(workspace.openJar(writeMultiReleaseFamilyFixture().string()).is_ok()); - - // 内名命中族:确定序选 base 条目。 - auto resolved = workspace.readClassBytes("workspace/Subject"); - ASSERT_TRUE(resolved.is_ok()); - EXPECT_EQ(std::move(resolved).unwrap().resourceUri, std::string("workspace/Subject.class")); - - // 内名选择器替换:只落 base 条目,versions/17 变体字节不动。 - const auto replacement = assemble("workspace/Subject", "42"); - auto applied = workspace.replaceClassBytes("workspace/Subject", replacement, - expected(0)); - ASSERT_TRUE(applied.status.is_ok()); - EXPECT_TRUE(applied.committed); - - auto baseAfter = workspace.readClassBytes("workspace/Subject.class"); - ASSERT_TRUE(baseAfter.is_ok()); - EXPECT_EQ(std::move(baseAfter).unwrap().bytes, replacement); - - const auto original = assemble("workspace/Subject", "1"); - auto variantAfter = workspace.readClassBytes("META-INF/versions/17/workspace/Subject.class"); - ASSERT_TRUE(variantAfter.is_ok()); - EXPECT_EQ(std::move(variantAfter).unwrap().bytes, original); -} - -} // namespace mjbase::workspace diff --git a/mjbase/test/unittest/workspace/workspace_validation_gate_test.cpp b/mjbase/test/unittest/workspace/workspace_validation_gate_test.cpp deleted file mode 100644 index 0fcdae7..0000000 --- a/mjbase/test/unittest/workspace/workspace_validation_gate_test.cpp +++ /dev/null @@ -1,315 +0,0 @@ -// 编辑校验门三档模式 + 作用域收窄验收面: -// - 协变捷径 fixture(putstatic HashMap 入 Map 字段 / String 以 CharSequence -// 接口调用 / String[] 作 Object[] 返回)在 relaxed(默认)门下全部通过, -// 在 strict(原精确匹配)门下如约报错——JVM 验证器接受的字节码不再卡死 -// 编辑账本; -// - 门禁作用域 = 操作受影响类:容器基线未编辑类不参与门禁(strict 模式下 -// 编辑 A 类也不再被基线 B 类的既存形态拒绝); -// - off 档跳过门禁;失败诊断按类聚合(per-class 单条目 + 截断明细)。 -// 底层语义(mjbase jasm relaxed verifier)另见 jasm_service_test 的 relaxed -// 直检用例;本文件走 WorkspaceBase 公开编辑事务面。 -#include -#include - -#include -#include -#include -#include - -#include "mjbase/jasm/jvm_jasm_service.hpp" -#include "mjbase/model/resource_container.hpp" -#include "mjbase/model/resource_helper.hpp" -#include "mjbase/model/resource_model.hpp" -#include "mjbase/workspace/workspace.hpp" - -namespace mjbase::workspace { -namespace model = mjbase::model; - -namespace { - -ca::str::Utf16String utf16(const std::string& value) -{ - return ca::str::Utf16String::from_utf8_string(ca::str::Utf8StringRef::from_data( - reinterpret_cast(value.data()), value.size())); -} - -/// 协变捷径类:三条 ZKM 风格"协变捷径"字节码(javac 不生成、JVM 赋值协变 -/// 验证合法、JDK21 实测可运行),外加一条普通可编辑类共用的工作台方法。 -std::string covariantSource() -{ - return ".super java/lang/Object\n" - ".class public super example/Covariant {\n" - " .field public static cache Ljava/util/Map;\n" - " .method public static setup ()V {\n" - " code: {\n" - " new java/util/HashMap\n" - " dup\n" - " invokespecial java/util/HashMap. ()V\n" - " putstatic example/Covariant.cache Ljava/util/Map;\n" - " return\n" - " }\n" - " }\n" - " .method public static measure (Ljava/lang/String;)I {\n" - " parameters: { text },\n" - " code: {\n" - " aload text\n" - " invokeinterface java/lang/CharSequence.length ()I\n" - " ireturn\n" - " }\n" - " }\n" - " .method public static view ()[Ljava/lang/Object; {\n" - " code: {\n" - " iconst_1\n" - " anewarray java/lang/String\n" - " dup\n" - " iconst_0\n" - " ldc \"a\"\n" - " aastore\n" - " astore names\n" - " aload names\n" - " areturn\n" - " }\n" - " }\n" - "}\n"; -} - -std::string subjectSource(const std::string& value) -{ - return ".super java/lang/Object\n" - ".class public super workspace/Subject {\n" - " .method public static value ()I {\n" - " code: {\n" - " bipush " + - value + - "\n" - " ireturn\n" - " }\n" - " }\n" - "}\n"; -} - -std::vector assemble(const std::string& name, const std::string& source) -{ - jasm::JvmJasmService service; - auto result = service.assemble(utf16(name + ".jasm").ref(), - utf16(source).ref()); - if (!result.isOk()) throw std::runtime_error("gate fixture did not assemble"); - return result.value->bytes; -} - -/// 双类 JAR 夹具:example/Covariant(基线协变捷径,编辑中从不触碰)+ -/// workspace/Subject(受编辑类)。 -std::filesystem::path writeGateFixture(const std::string& fileName) -{ - model::ResourceContainer container; - container.addResource("example/Covariant.class", - std::make_unique( - "example/Covariant.class", assemble("example/Covariant", - covariantSource()))); - container.addResource("workspace/Subject.class", - std::make_unique( - "workspace/Subject.class", - assemble("workspace/Subject", subjectSource("1")))); - const auto path = test::getTempOutPath(fileName); - model::ResourceHelper::exportToJar(container, path.string()); - return path; -} - -MutationRequest request(ca::u64 revision, jasm::ValidationMode mode) -{ - MutationRequest options; - options.expectedRevision = revision; - options.validationMode = mode; - return options; -} - -std::vector subjectBytes(const WorkspaceBase& workspace) -{ - auto content = workspace.readClassBytes("workspace/Subject"); - EXPECT_TRUE(content.is_ok()); - return content.is_ok() ? std::move(content).unwrap().bytes : std::vector{}; -} - -std::vector covariantBytes(const WorkspaceBase& workspace) -{ - auto content = workspace.readClassBytes("example/Covariant"); - EXPECT_TRUE(content.is_ok()); - return content.is_ok() ? std::move(content).unwrap().bytes : std::vector{}; -} - -} // namespace - -TEST(MjWorkspaceValidationGateTest, relaxedGatePassesCovariantShortcutsAndEditsFlow) -{ - WorkspaceBase workspace; - const auto input = writeGateFixture("mjbase_workspace_gate_covariant.jar"); - ASSERT_TRUE(workspace.openJar(input.string()).is_ok()); - const auto originalBytes = subjectBytes(workspace); - - // 编辑 → undo → revert → replace bytes 全链路不再被基线协变类卡死 - // (阻断场景验收;默认 relaxed,无需传参)。 - ASSERT_TRUE(workspace.applyClassBytes( - "workspace/Subject.class", assemble("workspace/Subject", subjectSource("42")), - request(0, jasm::ValidationMode::Relaxed)).status.is_ok()); - const auto editedBytes = subjectBytes(workspace); - EXPECT_NE(originalBytes, editedBytes); - - auto undone = workspace.undo(request(1, jasm::ValidationMode::Relaxed)); - ASSERT_TRUE(undone.status.is_ok()); - EXPECT_EQ(originalBytes, subjectBytes(workspace)); - - auto redone = workspace.redo(request(2, jasm::ValidationMode::Relaxed)); - ASSERT_TRUE(redone.status.is_ok()); - EXPECT_EQ(editedBytes, subjectBytes(workspace)); - - auto reverted = workspace.revertClass("workspace/Subject.class", - request(3, jasm::ValidationMode::Relaxed)); - ASSERT_TRUE(reverted.status.is_ok()); - EXPECT_EQ(originalBytes, subjectBytes(workspace)); -} - -TEST(MjWorkspaceValidationGateTest, strictGateRejectsCovariantShortcuts) -{ - jasm::JvmJasmService service; - jasm::JvmJasmContext strict; - strict.validationMode = jasm::ValidationMode::Strict; - auto covariant = assemble("example/Covariant", covariantSource()); - - // strict(原行为):三条协变捷径各自报错——作为 relaxed 修复的对照面。 - auto validated = service.validate(covariant, strict); - EXPECT_FALSE(validated.isOk()); - ca::usize errorCount = 0; - for (const auto& diagnostic : validated.diagnostics) { - if (diagnostic.severity == jasm::DiagnosticSeverity::Error) ++errorCount; - } - EXPECT_GE(errorCount, 3U); - - // relaxed(默认):同一字节全部通过。 - jasm::JvmJasmContext relaxed; // 默认 Relaxed - auto accepted = service.validate(covariant, relaxed); - EXPECT_TRUE(accepted.isOk()); - ca::usize relaxedErrors = 0; - for (const auto& diagnostic : accepted.diagnostics) { - if (diagnostic.severity == jasm::DiagnosticSeverity::Error) ++relaxedErrors; - } - EXPECT_EQ(0U, relaxedErrors); -} - -TEST(MjWorkspaceValidationGateTest, gateScopeCoversOnlyAffectedClasses) -{ - WorkspaceBase workspace; - const auto input = writeGateFixture("mjbase_workspace_gate_scope.jar"); - ASSERT_TRUE(workspace.openJar(input.string()).is_ok()); - const auto covariant = covariantBytes(workspace); - - // strict 门 + 作用域收窄:编辑 Subject 时基线 Covariant 类不参与 - // 门禁——其既存形态不再拒绝无关编辑。 - auto edited = workspace.applyClassBytes( - "workspace/Subject.class", assemble("workspace/Subject", subjectSource("7")), - request(0, jasm::ValidationMode::Strict)); - ASSERT_TRUE(edited.status.is_ok()); - - // 受影响类本身带协变捷径时,strict 门如约拒绝(作用域内的真实严格语义; - // 拒绝不消耗 revision,后续请求沿用当前位)。 - auto strictSelfEdit = workspace.replaceClassBytes("example/Covariant", covariant, - request(1, jasm::ValidationMode::Strict)); - EXPECT_FALSE(strictSelfEdit.status.is_ok()); - // 失败诊断按类聚合:per-class 单条目(类名 + 计数 + 明细),不再 - // 逐方法刷屏。 - ASSERT_EQ(1U, strictSelfEdit.diagnostics.size()); - const auto summary = strictSelfEdit.diagnostics[0].message.to_utf8_string().to_std_string(); - EXPECT_NE(std::string::npos, summary.find("example/Covariant")); - EXPECT_NE(std::string::npos, summary.find("error(s)")); - EXPECT_NE(std::string::npos, summary.find("\n Method ")); - // 编辑门诊断归因 EditGate 阶段(与重建管线的 Validate 阶段可区分)。 - EXPECT_EQ(jasm::DiagnosticStage::EditGate, strictSelfEdit.diagnostics[0].stage); - - // 同一替换在 relaxed(默认)门下通过——JVM 验证器会接受。 - auto relaxedSelfEdit = workspace.replaceClassBytes( - "example/Covariant", covariant, request(kAnyRevision, jasm::ValidationMode::Relaxed)); - ASSERT_TRUE(relaxedSelfEdit.status.is_ok()); -} - -TEST(MjWorkspaceValidationGateTest, offModeSkipsGateEntirely) -{ - WorkspaceBase workspace; - const auto input = writeGateFixture("mjbase_workspace_gate_off.jar"); - ASSERT_TRUE(workspace.openJar(input.string()).is_ok()); - - // off 档:受影响类带 strict 违规也直通。 - auto skipped = workspace.replaceClassBytes( - "example/Covariant", covariantBytes(workspace), request(0, jasm::ValidationMode::Off)); - ASSERT_TRUE(skipped.status.is_ok()); - EXPECT_TRUE(skipped.diagnostics.empty()); -} - -TEST(MjWorkspaceValidationGateTest, rebuildGateFollowsValidationMode) -{ - WorkspaceBase workspace; - const auto input = writeGateFixture("mjbase_workspace_gate_rebuild.jar"); - ASSERT_TRUE(workspace.openJar(input.string()).is_ok()); - - // jasm 重建管线自带校验门随 validation 档位(此前档位不进重建门, - // 三档诊断恒同):strict 下重汇编协变捷径文本如约拒绝,诊断归因 - // 重建门的 Validate 阶段而非编辑门。 - auto rejected = workspace.validateJasm("example/Covariant.class", - utf16("example/Covariant.jasm").ref(), - utf16(covariantSource()).ref(), - request(kAnyRevision, jasm::ValidationMode::Strict)); - EXPECT_FALSE(rejected.status.is_ok()); - ASSERT_FALSE(rejected.diagnostics.empty()); - bool rebuildStage = false; - for (const auto& diagnostic : rejected.diagnostics) { - if (diagnostic.stage == jasm::DiagnosticStage::Validate) rebuildStage = true; - } - EXPECT_TRUE(rebuildStage); - - // off 连重建校验一起跳过(汇编本身仍必须成功),同一文本直通。 - auto skipped = workspace.validateJasm("example/Covariant.class", - utf16("example/Covariant.jasm").ref(), - utf16(covariantSource()).ref(), - request(kAnyRevision, jasm::ValidationMode::Off)); - EXPECT_TRUE(skipped.status.is_ok()); - EXPECT_TRUE(skipped.diagnostics.empty()); -} - -TEST(MjWorkspaceValidationGateTest, rebuildGateResolvesBareInternalName) -{ - WorkspaceBase workspace; - const auto input = writeGateFixture("mjbase_workspace_gate_rebuild_bare.jar"); - ASSERT_TRUE(workspace.openJar(input.string()).is_ok()); - - // jasm 补丁选择器与结构化补丁同口径:裸内部名按字节内容解析到规范 - // URI(此前只有 replaceClassBytes/applyClassMutation 解析,jasm 路径 - // 传裸名报资源不存在)。 - auto roundTrip = workspace.validateJasm("workspace/Subject", - utf16("workspace/Subject.jasm").ref(), - utf16(subjectSource("1")).ref(), - request(kAnyRevision, jasm::ValidationMode::Off)); - ASSERT_TRUE(roundTrip.status.is_ok()) << roundTrip.status.message; - EXPECT_TRUE(roundTrip.diagnostics.empty()); -} - -TEST(MjWorkspaceValidationGateTest, structuredWriteBackGateFollowsValidationMode) -{ - WorkspaceBase workspace; - const auto input = writeGateFixture("mjbase_workspace_gate_writeback.jar"); - ASSERT_TRUE(workspace.openJar(input.string()).is_ok()); - - // 结构化编辑写回后的整类校验门随档位:strict 下基线协变捷径如约拒绝 - // (该门此前恒用默认档,三档无差别、off 也拦)。 - ClassMutation strictEdit; - strictEdit.classUri = "example/Covariant.class"; - strictEdit.kind = ClassMutationKind::ChangeClassAccess; - strictEdit.setFlags = {"final"}; - auto rejected = - workspace.applyClassMutation(strictEdit, request(0, jasm::ValidationMode::Strict)); - EXPECT_FALSE(rejected.status.is_ok()); - - // off 直通:同编辑照常入账(写回门与受影响类的编辑门同步跳过)。 - auto accepted = workspace.applyClassMutation( - strictEdit, request(kAnyRevision, jasm::ValidationMode::Off)); - ASSERT_TRUE(accepted.status.is_ok()) << accepted.status.message; -} - -} // namespace mjbase::workspace diff --git a/mjbase/xmake.lua b/mjbase/xmake.lua index 7a57afa..5b84ea6 100644 --- a/mjbase/xmake.lua +++ b/mjbase/xmake.lua @@ -112,14 +112,12 @@ target("mjbase") add_files("src/mjbase/spi/**.cpp") add_files("src/mjbase/pass/**.cpp") add_files("src/mjbase/plugin/**.cpp") - add_files("src/mjbase/asm/classpath/resolved_call_graph.cpp") add_files("src/mjbase/asm/remapper/**.cpp") - -- 批 4:mcp 与 mjt-py 的直接消费面——renamer、workspace、jasm、log、cli。 - -- workspace 依赖 jasm,故 jasm 从批 5 提前到本批;cli 只收源码(文案表 - -- .lang 的规则在上方始终保留)。 + -- 批 4:mjt 与 mjt-py 的直接消费面——renamer、jasm、log、cli。 + -- (workspace 已整体退役移交下游,jasm 仍留本批供 cli/验证面消费; + -- cli 只收源码(文案表 .lang 的规则在上方始终保留)。) add_files("src/mjbase/renamer/**.cpp") add_files("src/mjbase/jasm/**.cpp") - add_files("src/mjbase/workspace/**.cpp") add_files("src/mjbase/cli/*.cpp") -- cli 的 ArgParser 文案单源表(i18n_setup 静态注册,public include 供下游覆盖)。 -- 批 1 不含 cli 源码,但 i18n.hpp 依赖其生成头,故规则始终保留。 @@ -182,11 +180,10 @@ target("mjbase_unittest") -- golden 对拍子目录(旧栈 baseline 字节/指纹对拍机器,pass-golden-pipeline.md)。 add_files("test/unittest/pass/golden/*_test.cpp") add_files("test/unittest/asm/remapper/*_test.cpp") - -- 批 4:与上面 src 的 renamer/jasm/log/workspace/cli 一一对应。 + -- 批 4:与上面 src 的 renamer/jasm/log/cli 一一对应(workspace 测试随模块退役)。 add_files("test/unittest/renamer/*_test.cpp") add_files("test/unittest/jasm/*_test.cpp") add_files("test/unittest/log/*_test.cpp") - add_files("test/unittest/workspace/*_test.cpp") add_files("test/unittest/cli/*_test.cpp") -- 第 5 批(孤儿测试救回):keys/i18n/util 伞头与叶子件, -- 与 src 的 keys.hpp/i18n.hpp/util 一一对应(B3-A 波次测试面,副本删除前收容)。