diff --git a/mjt-py/src/bind_core.cpp b/mjt-py/src/bind_core.cpp index d8891ef..2307627 100644 --- a/mjt-py/src/bind_core.cpp +++ b/mjt-py/src/bind_core.cpp @@ -20,7 +20,7 @@ #include "mjbase/spi/pass_registry.hpp" #include "mjbase/spi/pipeline_builder.hpp" #include "mjbase/spi/strict_type_resolver.hpp" -#include "mjbase/workspace/workspace.hpp" +#include "workspace/workspace.hpp" #include "pysdk_bridge.hpp" @@ -232,7 +232,7 @@ struct JdkResolverChain if (jdkPath.empty()) return; pool = std::make_unique(); hierarchy = std::make_unique(*pool); - mjbase::workspace::scanJdkHierarchy(jdkPath, *hierarchy, *pool); + mjt::workspace::scanJdkHierarchy(jdkPath, *hierarchy, *pool); superResolver = std::make_unique(*hierarchy); typeResolver = std::make_unique(*superResolver); } diff --git a/mjt-py/xmake.lua b/mjt-py/xmake.lua index 8581046..a475a72 100644 --- a/mjt-py/xmake.lua +++ b/mjt-py/xmake.lua @@ -69,6 +69,7 @@ if has_config("with_pysdk") then "src/pysdk_limits.cpp", "src/bind_core.cpp", "src/bind_facade.cpp") add_files(path.join(mjtSharedSrc, "shared", "decompiler.cpp"), path.join(mjtSharedSrc, "shared", "jar_remapper.cpp")) + add_files(path.join(mjtSharedSrc, "workspace", "**.cpp")) -- xmake 3.x 不把静态 target 依赖传进 shared 链接:显式给全闭包,按 -- 「消费者在前、被依赖在后」排序(MSVC 对序不敏感,此序为 GNU ld 兼容 @@ -101,6 +102,7 @@ if has_config("with_pysdk") then "src/mjt_pysdk_unittest.cpp", "src/mjt_pysdk_facade_test.cpp") add_files(path.join(mjtSharedSrc, "shared", "decompiler.cpp"), path.join(mjtSharedSrc, "shared", "jar_remapper.cpp")) + add_files(path.join(mjtSharedSrc, "workspace", "**.cpp")) mjt_binary_link_closure("mjbase", "libjhasm", "libjclassfile", "libjstr", "libjbc", "libjzip") diff --git a/mjt/mcp/src/tool/contract.cpp b/mjt/mcp/src/tool/contract.cpp index 6923620..0cbcfcc 100644 --- a/mjt/mcp/src/tool/contract.cpp +++ b/mjt/mcp/src/tool/contract.cpp @@ -725,7 +725,7 @@ Json toolsList() {"content_base64", {{"type", "string"}, {"minLength", 4}, - {"maxLength", ((mjbase::workspace::kMaximumClassReplacementSize + 2) / 3) * 4}}}, + {"maxLength", ((mjt::workspace::kMaximumClassReplacementSize + 2) / 3) * 4}}}, {"pipeline", pipeline.at("pipeline")}, {"passes", pipeline.at("passes")}, {"jdk_path", pipeline.at("jdk_path")}, diff --git a/mjt/mcp/src/tool/decompiler.cpp b/mjt/mcp/src/tool/decompiler.cpp index 77f4fb2..89b2890 100644 --- a/mjt/mcp/src/tool/decompiler.cpp +++ b/mjt/mcp/src/tool/decompiler.cpp @@ -21,12 +21,12 @@ namespace mjt::mcp { using namespace detail; -using mjbase::workspace::MutationRequest; -using mjbase::workspace::PipelineRequest; -using mjbase::workspace::WorkflowCandidateCommitRequest; -using mjbase::workspace::WorkflowReferenceQuery; -using mjbase::workspace::WorkflowRequest; -using mjbase::workspace::IWorkspace; +using mjt::workspace::MutationRequest; +using mjt::workspace::PipelineRequest; +using mjt::workspace::WorkflowCandidateCommitRequest; +using mjt::workspace::WorkflowReferenceQuery; +using mjt::workspace::WorkflowRequest; +using mjt::workspace::IWorkspace; namespace fs = std::filesystem; diff --git a/mjt/mcp/src/tool/decompiler_support.cpp b/mjt/mcp/src/tool/decompiler_support.cpp index a697070..30b788b 100644 --- a/mjt/mcp/src/tool/decompiler_support.cpp +++ b/mjt/mcp/src/tool/decompiler_support.cpp @@ -13,7 +13,7 @@ namespace mjt::mcp::detail { namespace fs = std::filesystem; -using mjbase::workspace::IWorkspace; +using mjt::workspace::IWorkspace; ScopedTemporaryDirectory::ScopedTemporaryDirectory(fs::path path) : path_(std::move(path)) diff --git a/mjt/mcp/src/tool/decompiler_support.hpp b/mjt/mcp/src/tool/decompiler_support.hpp index fa16218..db93888 100644 --- a/mjt/mcp/src/tool/decompiler_support.hpp +++ b/mjt/mcp/src/tool/decompiler_support.hpp @@ -55,7 +55,7 @@ ca::StatusOr decompileSnapshotClass( const DecompilerOptions& options, const std::function& cancellationRequested = {}); ca::StatusOr decompileWorkspaceClass( - mjbase::workspace::IWorkspace& workspace, + mjt::workspace::IWorkspace& workspace, const std::string& className, const DecompilerOptions& options); Json decompiledSourceJson(const DecompiledSource& result, const char* sourceScope, diff --git a/mjt/mcp/src/tool/json.cpp b/mjt/mcp/src/tool/json.cpp index 7eee366..b7cf9d7 100644 --- a/mjt/mcp/src/tool/json.cpp +++ b/mjt/mcp/src/tool/json.cpp @@ -11,20 +11,20 @@ namespace mjt::mcp::detail { -using mjbase::workspace::AnnotationElement; -using mjbase::workspace::AnnotationInfo; -using mjbase::workspace::ClassInfo; -using mjbase::workspace::ClassSummary; -using mjbase::workspace::FieldInfo; -using mjbase::workspace::ImpactAnalysis; -using mjbase::workspace::JarRepairResult; -using mjbase::workspace::MethodInfo; -using mjbase::workspace::MutationResult; -using mjbase::workspace::SearchHit; -using mjbase::workspace::SupportingResourceInfo; -using mjbase::workspace::WorkflowReferenceKind; -using mjbase::workspace::WorkflowResult; -using mjbase::workspace::IWorkspace; +using mjt::workspace::AnnotationElement; +using mjt::workspace::AnnotationInfo; +using mjt::workspace::ClassInfo; +using mjt::workspace::ClassSummary; +using mjt::workspace::FieldInfo; +using mjt::workspace::ImpactAnalysis; +using mjt::workspace::JarRepairResult; +using mjt::workspace::MethodInfo; +using mjt::workspace::MutationResult; +using mjt::workspace::SearchHit; +using mjt::workspace::SupportingResourceInfo; +using mjt::workspace::WorkflowReferenceKind; +using mjt::workspace::WorkflowResult; +using mjt::workspace::IWorkspace; ca::str::Utf16String utf16(const std::string& value) { @@ -526,7 +526,7 @@ Json jarRepairJson(const JarRepairResult& repair) return result; } -Json referenceJson(const mjbase::workspace::ReferenceInfo& reference) +Json referenceJson(const mjt::workspace::ReferenceInfo& reference) { return {{"source", {{"class", reference.sourceClass}, {"method", reference.sourceMethod}, @@ -610,7 +610,7 @@ std::string base64(const std::vector& value) bool decodeBase64(const std::string& encoded, std::vector& output, std::string& error) { constexpr ca::usize kMaximumEncodedClassSize = - ((mjbase::workspace::kMaximumClassReplacementSize + 2) / 3) * 4; + ((mjt::workspace::kMaximumClassReplacementSize + 2) / 3) * 4; if (encoded.empty()) { error = "content_base64 must not be empty"; return false; @@ -656,7 +656,7 @@ bool decodeBase64(const std::string& encoded, std::vector& output, std:: if (!pad2) output.push_back(static_cast((v1 << 4) | (v2 >> 2))); if (!pad3) output.push_back(static_cast((v2 << 6) | v3)); } - if (output.size() > mjbase::workspace::kMaximumClassReplacementSize) { + if (output.size() > mjt::workspace::kMaximumClassReplacementSize) { error = "decoded class exceeds the class size limit"; output.clear(); return false; diff --git a/mjt/mcp/src/tool/json.hpp b/mjt/mcp/src/tool/json.hpp index 90f465d..3242227 100644 --- a/mjt/mcp/src/tool/json.hpp +++ b/mjt/mcp/src/tool/json.hpp @@ -14,9 +14,9 @@ std::string utf8(ca::str::Utf16StringRef value); std::string lowercased(const std::string& value); Json statusJson(const ca::Status& status); Json diagnosticJson(const mjbase::jasm::Diagnostic& diagnostic); -Json methodJson(const mjbase::workspace::MethodInfo& method); -Json supportingResourceJson(const mjbase::workspace::SupportingResourceInfo& support); -Json classJson(const mjbase::workspace::ClassInfo& clazz); +Json methodJson(const mjt::workspace::MethodInfo& method); +Json supportingResourceJson(const mjt::workspace::SupportingResourceInfo& support); +Json classJson(const mjt::workspace::ClassInfo& clazz); ///@brief instructions 搜索命中 value(拼接文本)的结构化拆解。 /// @@ -90,11 +90,11 @@ std::string selectorKeyError(const std::string& key, const std::string& value, const std::vector& expected); -Json searchHitJson(const mjbase::workspace::SearchHit& hit); -Json mutationJson(const mjbase::workspace::MutationResult& mutation); -Json jarRepairJson(const mjbase::workspace::JarRepairResult& repair); -Json referenceJson(const mjbase::workspace::ReferenceInfo& reference); -Json workflowJson(const mjbase::workspace::WorkflowResult& workflow); +Json searchHitJson(const mjt::workspace::SearchHit& hit); +Json mutationJson(const mjt::workspace::MutationResult& mutation); +Json jarRepairJson(const mjt::workspace::JarRepairResult& repair); +Json referenceJson(const mjt::workspace::ReferenceInfo& reference); +Json workflowJson(const mjt::workspace::WorkflowResult& workflow); Json toolResult(Json payload, bool isError = false); Json toolError(const std::string& message, const ca::Status* status = nullptr); std::string base64(const std::vector& value); @@ -128,14 +128,14 @@ bool parseCursor(const Json& arguments, ca::usize total, ca::usize& offset); ///@return true 时 out 携带分页文档;false 时 error 携带结构化 toolError。 bool page(Json values, const Json& arguments, Json& out, std::string& error, const std::vector& rowKeys); -bool classSummaryPage(const std::vector& values, +bool classSummaryPage(const std::vector& values, const Json& arguments, Json& out, std::string& error); -Json impactJson(const mjbase::workspace::ImpactAnalysis& impact); +Json impactJson(const mjt::workspace::ImpactAnalysis& impact); Json normalizeToolError(const std::string& name, const Json& arguments, Json result, - const mjbase::workspace::IWorkspace* workspace); + const mjt::workspace::IWorkspace* workspace); } //namespace mjt::mcp::detail diff --git a/mjt/mcp/src/tool/mutation.cpp b/mjt/mcp/src/tool/mutation.cpp index 8556eb5..4c8e1c1 100644 --- a/mjt/mcp/src/tool/mutation.cpp +++ b/mjt/mcp/src/tool/mutation.cpp @@ -22,10 +22,10 @@ namespace mjt::mcp { using namespace detail; -using mjbase::workspace::ClassMutation; -using mjbase::workspace::PipelineRequest; -using mjbase::workspace::RenameRequest; -using mjbase::workspace::IWorkspace; +using mjt::workspace::ClassMutation; +using mjt::workspace::PipelineRequest; +using mjt::workspace::RenameRequest; +using mjt::workspace::IWorkspace; std::optional McpToolService::callMutationTool( const std::string& name, const Json& arguments, ProjectSession& session) @@ -582,8 +582,8 @@ std::optional McpToolService::callMutationTool( } //显式 if-else 分派替代原 3 层嵌套三元,避免重复读取 arguments 与拷贝; //用 lambda 单次构造返回。 - auto query = [&]() -> mjbase::workspace::WorkspaceResult< - std::vector> { + auto query = [&]() -> mjt::workspace::WorkspaceResult< + std::vector> { if (name == "xref_method") { return current->xrefsToMethod(owner, member, descriptor); } @@ -616,7 +616,7 @@ std::optional McpToolService::callMutationTool( if (record.role == wanted) roleSupportIds.insert(record.supportId); items.erase(std::remove_if(items.begin(), items.end(), - [&](const mjbase::workspace::ReferenceInfo& item) { + [&](const mjt::workspace::ReferenceInfo& item) { //保留 = 站点在本角色容器内;其余全部剔除。 if (!item.supporting) return true; if (roleSupportIds.count(item.supportId) == 0) @@ -629,7 +629,7 @@ std::optional McpToolService::callMutationTool( else if (roleFilter == "primary") { items.erase(std::remove_if(items.begin(), items.end(), - [](const mjbase::workspace::ReferenceInfo& item) { + [](const mjt::workspace::ReferenceInfo& item) { return item.supporting; }), items.end()); @@ -637,7 +637,7 @@ std::optional McpToolService::callMutationTool( else if (roleFilter == "support") { items.erase(std::remove_if(items.begin(), items.end(), - [&](const mjbase::workspace::ReferenceInfo& item) { + [&](const mjt::workspace::ReferenceInfo& item) { if (!item.supporting) return false; return findMountedContainer(session, item.supportId) == nullptr; diff --git a/mjt/mcp/src/tool/parser.cpp b/mjt/mcp/src/tool/parser.cpp index e0459ac..8acb119 100644 --- a/mjt/mcp/src/tool/parser.cpp +++ b/mjt/mcp/src/tool/parser.cpp @@ -8,21 +8,21 @@ namespace mjt::mcp::detail { -using mjbase::workspace::ClassMutation; -using mjbase::workspace::ClassMutationKind; -using mjbase::workspace::ConstantKind; -using mjbase::workspace::ConstantSpec; -using mjbase::workspace::HandleSpec; -using mjbase::workspace::InstructionKind; -using mjbase::workspace::JarRepairRequest; -using mjbase::workspace::MutationRequest; -using mjbase::workspace::PipelineRequest; -using mjbase::workspace::RenameKind; -using mjbase::workspace::RenameRequest; -using mjbase::workspace::SearchMatchMode; -using mjbase::workspace::SearchOptions; -using mjbase::workspace::WorkflowReferenceKind; -using mjbase::workspace::WorkflowReferenceQuery; +using mjt::workspace::ClassMutation; +using mjt::workspace::ClassMutationKind; +using mjt::workspace::ConstantKind; +using mjt::workspace::ConstantSpec; +using mjt::workspace::HandleSpec; +using mjt::workspace::InstructionKind; +using mjt::workspace::JarRepairRequest; +using mjt::workspace::MutationRequest; +using mjt::workspace::PipelineRequest; +using mjt::workspace::RenameKind; +using mjt::workspace::RenameRequest; +using mjt::workspace::SearchMatchMode; +using mjt::workspace::SearchOptions; +using mjt::workspace::WorkflowReferenceKind; +using mjt::workspace::WorkflowReferenceQuery; bool parseSearchOptions(const Json& arguments, SearchOptions& output, std::string& error) { @@ -150,7 +150,7 @@ bool parseConstant(const Json& value, ConstantSpec& output, std::string& error, return true; } -bool parseInstruction(const Json& value, mjbase::workspace::InstructionSpec& output, +bool parseInstruction(const Json& value, mjt::workspace::InstructionSpec& output, std::string& error) { if (!value.is_object()) { @@ -234,34 +234,34 @@ bool parseInstruction(const Json& value, mjbase::workspace::InstructionSpec& out return true; } -bool parseAccessFlags(const Json& values, mjbase::workspace::AnnotationTarget target, +bool parseAccessFlags(const Json& values, mjt::workspace::AnnotationTarget target, ca::i32& output, std::string& error) { //JVM access 位(JVMS 表 4.1/4.4/4.5/4.6/4.7):mbase Opcodes::ACC_* 的 //mjbase 对应物是 workspace_types 的 kAcc* 常量集;此处按旧名起别名,保 //调用点零漂移。 - constexpr ca::u32 ACC_PUBLIC = mjbase::workspace::kAccPublic; - constexpr ca::u32 ACC_PRIVATE = mjbase::workspace::kAccPrivate; - constexpr ca::u32 ACC_PROTECTED = mjbase::workspace::kAccProtected; - constexpr ca::u32 ACC_STATIC = mjbase::workspace::kAccStatic; - constexpr ca::u32 ACC_FINAL = mjbase::workspace::kAccFinal; - constexpr ca::u32 ACC_SUPER = mjbase::workspace::kAccSuper; - constexpr ca::u32 ACC_SYNCHRONIZED = mjbase::workspace::kAccSynchronized; - constexpr ca::u32 ACC_VOLATILE = mjbase::workspace::kAccVolatile; - constexpr ca::u32 ACC_BRIDGE = mjbase::workspace::kAccBridge; - constexpr ca::u32 ACC_TRANSIENT = mjbase::workspace::kAccTransient; - constexpr ca::u32 ACC_VARARGS = mjbase::workspace::kAccVarargs; - constexpr ca::u32 ACC_NATIVE = mjbase::workspace::kAccNative; - constexpr ca::u32 ACC_INTERFACE = mjbase::workspace::kAccInterface; - constexpr ca::u32 ACC_ABSTRACT = mjbase::workspace::kAccAbstract; - constexpr ca::u32 ACC_STRICT = mjbase::workspace::kAccStrict; - constexpr ca::u32 ACC_SYNTHETIC = mjbase::workspace::kAccSynthetic; - constexpr ca::u32 ACC_ANNOTATION = mjbase::workspace::kAccAnnotation; - constexpr ca::u32 ACC_ENUM = mjbase::workspace::kAccEnum; - constexpr ca::u32 ACC_MODULE = mjbase::workspace::kAccModule; - constexpr ca::u32 ACC_RECORD = mjbase::workspace::kAccRecord; - constexpr ca::u32 ACC_SEALED = mjbase::workspace::kAccSealed; - constexpr ca::u32 ACC_DEPRECATED = mjbase::workspace::kAccDeprecated; + constexpr ca::u32 ACC_PUBLIC = mjt::workspace::kAccPublic; + constexpr ca::u32 ACC_PRIVATE = mjt::workspace::kAccPrivate; + constexpr ca::u32 ACC_PROTECTED = mjt::workspace::kAccProtected; + constexpr ca::u32 ACC_STATIC = mjt::workspace::kAccStatic; + constexpr ca::u32 ACC_FINAL = mjt::workspace::kAccFinal; + constexpr ca::u32 ACC_SUPER = mjt::workspace::kAccSuper; + constexpr ca::u32 ACC_SYNCHRONIZED = mjt::workspace::kAccSynchronized; + constexpr ca::u32 ACC_VOLATILE = mjt::workspace::kAccVolatile; + constexpr ca::u32 ACC_BRIDGE = mjt::workspace::kAccBridge; + constexpr ca::u32 ACC_TRANSIENT = mjt::workspace::kAccTransient; + constexpr ca::u32 ACC_VARARGS = mjt::workspace::kAccVarargs; + constexpr ca::u32 ACC_NATIVE = mjt::workspace::kAccNative; + constexpr ca::u32 ACC_INTERFACE = mjt::workspace::kAccInterface; + constexpr ca::u32 ACC_ABSTRACT = mjt::workspace::kAccAbstract; + constexpr ca::u32 ACC_STRICT = mjt::workspace::kAccStrict; + constexpr ca::u32 ACC_SYNTHETIC = mjt::workspace::kAccSynthetic; + constexpr ca::u32 ACC_ANNOTATION = mjt::workspace::kAccAnnotation; + constexpr ca::u32 ACC_ENUM = mjt::workspace::kAccEnum; + constexpr ca::u32 ACC_MODULE = mjt::workspace::kAccModule; + constexpr ca::u32 ACC_RECORD = mjt::workspace::kAccRecord; + constexpr ca::u32 ACC_SEALED = mjt::workspace::kAccSealed; + constexpr ca::u32 ACC_DEPRECATED = mjt::workspace::kAccDeprecated; if (!values.is_array()) { error = "access_flags must be an array"; return false; @@ -285,39 +285,39 @@ bool parseAccessFlags(const Json& values, mjbase::workspace::AnnotationTarget ta else if (name == "final") flag = ACC_FINAL; else if (name == "synthetic") flag = ACC_SYNTHETIC; else if (name == "deprecated") flag = ACC_DEPRECATED; - else if (target == mjbase::workspace::AnnotationTarget::Class && name == "super") + else if (target == mjt::workspace::AnnotationTarget::Class && name == "super") flag = ACC_SUPER; - else if (target == mjbase::workspace::AnnotationTarget::Class && name == "interface") + else if (target == mjt::workspace::AnnotationTarget::Class && name == "interface") flag = ACC_INTERFACE; - else if (target == mjbase::workspace::AnnotationTarget::Class && name == "abstract") + else if (target == mjt::workspace::AnnotationTarget::Class && name == "abstract") flag = ACC_ABSTRACT; - else if (target == mjbase::workspace::AnnotationTarget::Class && name == "annotation") + else if (target == mjt::workspace::AnnotationTarget::Class && name == "annotation") flag = ACC_ANNOTATION; - else if (target == mjbase::workspace::AnnotationTarget::Class && name == "enum") + else if (target == mjt::workspace::AnnotationTarget::Class && name == "enum") flag = ACC_ENUM; - else if (target == mjbase::workspace::AnnotationTarget::Class && name == "module") + else if (target == mjt::workspace::AnnotationTarget::Class && name == "module") flag = ACC_MODULE; - else if (target == mjbase::workspace::AnnotationTarget::Class && name == "record") + else if (target == mjt::workspace::AnnotationTarget::Class && name == "record") flag = ACC_RECORD; - else if (target == mjbase::workspace::AnnotationTarget::Class && name == "sealed") + else if (target == mjt::workspace::AnnotationTarget::Class && name == "sealed") flag = ACC_SEALED; - else if (target == mjbase::workspace::AnnotationTarget::Field && name == "volatile") + else if (target == mjt::workspace::AnnotationTarget::Field && name == "volatile") flag = ACC_VOLATILE; - else if (target == mjbase::workspace::AnnotationTarget::Field && name == "transient") + else if (target == mjt::workspace::AnnotationTarget::Field && name == "transient") flag = ACC_TRANSIENT; - else if (target == mjbase::workspace::AnnotationTarget::Field && name == "enum") + else if (target == mjt::workspace::AnnotationTarget::Field && name == "enum") flag = ACC_ENUM; - else if (target == mjbase::workspace::AnnotationTarget::Method && name == "synchronized") + else if (target == mjt::workspace::AnnotationTarget::Method && name == "synchronized") flag = ACC_SYNCHRONIZED; - else if (target == mjbase::workspace::AnnotationTarget::Method && name == "bridge") + else if (target == mjt::workspace::AnnotationTarget::Method && name == "bridge") flag = ACC_BRIDGE; - else if (target == mjbase::workspace::AnnotationTarget::Method && name == "varargs") + else if (target == mjt::workspace::AnnotationTarget::Method && name == "varargs") flag = ACC_VARARGS; - else if (target == mjbase::workspace::AnnotationTarget::Method && name == "native") + else if (target == mjt::workspace::AnnotationTarget::Method && name == "native") flag = ACC_NATIVE; - else if (target == mjbase::workspace::AnnotationTarget::Method && name == "abstract") + else if (target == mjt::workspace::AnnotationTarget::Method && name == "abstract") flag = ACC_ABSTRACT; - else if (target == mjbase::workspace::AnnotationTarget::Method && name == "strict") + else if (target == mjt::workspace::AnnotationTarget::Method && name == "strict") flag = ACC_STRICT; else { error = "access flag is not valid for this target: " + name; @@ -395,13 +395,13 @@ bool parseMutation(const Json& arguments, ClassMutation& mutation, std::string& } mutation.access = arguments.value("access", 0); if (arguments.contains("access_flags")) { - mjbase::workspace::AnnotationTarget accessTarget; + mjt::workspace::AnnotationTarget accessTarget; if (mutation.kind == ClassMutationKind::SetFieldAccess || mutation.kind == ClassMutationKind::AddField) - accessTarget = mjbase::workspace::AnnotationTarget::Field; + accessTarget = mjt::workspace::AnnotationTarget::Field; else if (mutation.kind == ClassMutationKind::SetMethodAccess || mutation.kind == ClassMutationKind::AddMethod) - accessTarget = mjbase::workspace::AnnotationTarget::Method; + accessTarget = mjt::workspace::AnnotationTarget::Method; else { error = "access_flags is only valid for access setters and member additions"; return false; @@ -417,9 +417,9 @@ bool parseMutation(const Json& arguments, ClassMutation& mutation, std::string& mutation.annotationDescriptor = arguments.value("annotation_descriptor", std::string{}); mutation.annotationVisible = arguments.value("annotation_visible", true); const auto annotationTarget = arguments.value("annotation_target", std::string("class")); - if (annotationTarget == "class") mutation.annotationTarget = mjbase::workspace::AnnotationTarget::Class; - else if (annotationTarget == "field") mutation.annotationTarget = mjbase::workspace::AnnotationTarget::Field; - else if (annotationTarget == "method") mutation.annotationTarget = mjbase::workspace::AnnotationTarget::Method; + if (annotationTarget == "class") mutation.annotationTarget = mjt::workspace::AnnotationTarget::Class; + else if (annotationTarget == "field") mutation.annotationTarget = mjt::workspace::AnnotationTarget::Field; + else if (annotationTarget == "method") mutation.annotationTarget = mjt::workspace::AnnotationTarget::Method; else { error = "unsupported annotation target: " + annotationTarget; return false; @@ -459,7 +459,7 @@ bool parseMutation(const Json& arguments, ClassMutation& mutation, std::string& return false; } for (const auto& value : arguments.at("initial_instructions")) { - mjbase::workspace::InstructionSpec spec; + mjt::workspace::InstructionSpec spec; if (!parseInstruction(value, spec, error)) return false; mutation.initialInstructions.push_back(std::move(spec)); } diff --git a/mjt/mcp/src/tool/parser.hpp b/mjt/mcp/src/tool/parser.hpp index 18aa5dc..95af407 100644 --- a/mjt/mcp/src/tool/parser.hpp +++ b/mjt/mcp/src/tool/parser.hpp @@ -8,28 +8,28 @@ namespace mjt::mcp::detail { bool parseSearchOptions(const Json& arguments, - mjbase::workspace::SearchOptions& output, + mjt::workspace::SearchOptions& output, std::string& error); -mjbase::workspace::MutationRequest mutationRequest(const Json& arguments); +mjt::workspace::MutationRequest mutationRequest(const Json& arguments); bool parseMutation(const Json& arguments, - mjbase::workspace::ClassMutation& mutation, + mjt::workspace::ClassMutation& mutation, std::string& error); bool parseRename(const Json& arguments, - mjbase::workspace::RenameRequest& output, + mjt::workspace::RenameRequest& output, std::string& error); bool parsePipelineRequest(const Json& arguments, - mjbase::workspace::PipelineRequest& output, + mjt::workspace::PipelineRequest& output, std::string& error); bool parseWorkflowReferences( const Json& arguments, - std::vector& output, + std::vector& output, std::string& error); bool parseWorkflowDecompileClasses(const Json& arguments, std::vector& output, bool& batch, std::string& error); bool parseJarRepairRequest(const Json& arguments, - mjbase::workspace::JarRepairRequest& output, + mjt::workspace::JarRepairRequest& output, std::string& error); } //namespace mjt::mcp::detail diff --git a/mjt/mcp/src/tool/query.cpp b/mjt/mcp/src/tool/query.cpp index 9e94a8a..c5e5314 100644 --- a/mjt/mcp/src/tool/query.cpp +++ b/mjt/mcp/src/tool/query.cpp @@ -31,9 +31,9 @@ namespace mjt::mcp { using namespace detail; -using mjbase::workspace::SearchHit; -using mjbase::workspace::SearchOptions; -using mjbase::workspace::IWorkspace; +using mjt::workspace::SearchHit; +using mjt::workspace::SearchOptions; +using mjt::workspace::IWorkspace; namespace { @@ -608,7 +608,7 @@ std::optional McpToolService::callWorkspaceQueryTool( }; hitList.erase(std::remove_if(hitList.begin(), hitList.end(), - [&](const mjbase::workspace::SearchHit& hit) { + [&](const mjt::workspace::SearchHit& hit) { //判别文本面与 mjbase 的匹配面同源: //类行=内部名/URI,成员行=owner/name/ //descriptor/value,其余=拼接 value。 @@ -631,7 +631,7 @@ std::optional McpToolService::callWorkspaceQueryTool( const bool wantLiteral = stringKindFilter == "string_literal"; hitList.erase(std::remove_if(hitList.begin(), hitList.end(), - [&](const mjbase::workspace::SearchHit& hit) { + [&](const mjt::workspace::SearchHit& hit) { if (wantLiteral) return hit.kind != "constant_pool_string" && hit.kind != "resource_string"; @@ -651,7 +651,7 @@ std::optional McpToolService::callWorkspaceQueryTool( }; hitList.erase(std::remove_if(hitList.begin(), hitList.end(), - [&](const mjbase::workspace::SearchHit& hit) { + [&](const mjt::workspace::SearchHit& hit) { detail::InstructionHitParts parts; if (!detail::parseInstructionHitValue(hit.value, parts)) return true; diff --git a/mjt/mcp/src/tool/service.cpp b/mjt/mcp/src/tool/service.cpp index 20eb522..86aab5c 100644 --- a/mjt/mcp/src/tool/service.cpp +++ b/mjt/mcp/src/tool/service.cpp @@ -13,7 +13,7 @@ namespace mjt::mcp { using namespace detail; -using mjbase::workspace::IWorkspace; +using mjt::workspace::IWorkspace; std::string McpToolService::nextWorkspaceId() { diff --git a/mjt/mcp/src/tool/service.hpp b/mjt/mcp/src/tool/service.hpp index 033a15e..fa8e251 100644 --- a/mjt/mcp/src/tool/service.hpp +++ b/mjt/mcp/src/tool/service.hpp @@ -13,7 +13,7 @@ #include -#include "mjbase/workspace/i_workspace.hpp" +#include "workspace/i_workspace.hpp" #include "shared/jar_workspace.hpp" #include "tool/project_session.hpp" diff --git a/mjt/mcp/src/tool/session.cpp b/mjt/mcp/src/tool/session.cpp index e277ece..0fbd8e2 100644 --- a/mjt/mcp/src/tool/session.cpp +++ b/mjt/mcp/src/tool/session.cpp @@ -13,7 +13,7 @@ namespace mjt::mcp { using namespace detail; -using mjbase::workspace::IWorkspace; +using mjt::workspace::IWorkspace; Json jobStateJson(const std::shared_ptr& job) { diff --git a/mjt/mcp/src/tool/shadow_slot.cpp b/mjt/mcp/src/tool/shadow_slot.cpp index 31d3a42..d2638f8 100644 --- a/mjt/mcp/src/tool/shadow_slot.cpp +++ b/mjt/mcp/src/tool/shadow_slot.cpp @@ -10,7 +10,7 @@ #include "tool/json.hpp" #include "mjbase/mapping/shadow_alias_format.hpp" -#include "mjbase/workspace/i_workspace.hpp" +#include "workspace/i_workspace.hpp" namespace mjt::mcp::slot { @@ -33,7 +33,7 @@ Json slotError(const std::string& message) return detail::toolError(message); } -WorkspaceShadowTable loadWorkspaceShadowTable(mjbase::workspace::IWorkspace& workspace, +WorkspaceShadowTable loadWorkspaceShadowTable(mjt::workspace::IWorkspace& workspace, const std::string& file) { WorkspaceShadowTable slots; @@ -81,7 +81,7 @@ bool resolveClassSlot(const WorkspaceShadowTable& slots, return true; } -bool resourceUriOfResolved(mjbase::workspace::IWorkspace& workspace, +bool resourceUriOfResolved(mjt::workspace::IWorkspace& workspace, const std::string& internalName, std::string& uri, Json& error) @@ -159,7 +159,7 @@ bool resolveMethodSlots(const WorkspaceShadowTable& slots, return true; } -bool backfillMemberDescriptor(mjbase::workspace::IWorkspace& workspace, +bool backfillMemberDescriptor(mjt::workspace::IWorkspace& workspace, const std::string& ownerRaw, const std::string& memberName, bool field, @@ -222,7 +222,7 @@ bool backfillMemberDescriptor(mjbase::workspace::IWorkspace& workspace, return false; } -bool memberDescriptorMismatch(mjbase::workspace::IWorkspace& workspace, +bool memberDescriptorMismatch(mjt::workspace::IWorkspace& workspace, const std::string& ownerRaw, const std::string& memberName, const std::string& descriptor, @@ -262,7 +262,7 @@ bool memberDescriptorMismatch(mjbase::workspace::IWorkspace& workspace, return false; } -bool resolveClassArgument(mjbase::workspace::IWorkspace& workspace, +bool resolveClassArgument(mjt::workspace::IWorkspace& workspace, const Json& arguments, const std::string& key, bool verifyRaw, @@ -281,7 +281,7 @@ bool resolveClassArgument(mjbase::workspace::IWorkspace& workspace, return true; } -bool resolveClassUriArgument(mjbase::workspace::IWorkspace& workspace, +bool resolveClassUriArgument(mjt::workspace::IWorkspace& workspace, const Json& arguments, const std::string& key, std::string& out, diff --git a/mjt/mcp/src/tool/shadow_slot.hpp b/mjt/mcp/src/tool/shadow_slot.hpp index 1716cb7..8e9bfcc 100644 --- a/mjt/mcp/src/tool/shadow_slot.hpp +++ b/mjt/mcp/src/tool/shadow_slot.hpp @@ -8,7 +8,7 @@ #include "mjbase/renamer/shadow_alias.hpp" #include "mjbase/renamer/shadow_token.hpp" -namespace mjbase::workspace { +namespace mjt::workspace { class IWorkspace; } @@ -33,7 +33,7 @@ struct WorkspaceShadowTable ///@brief 加载 workspace 的缺省 sidecar(`.mjt-shadow`;`file` 显式覆盖)。 ///读取/解析失败不抛:记入 note 并按缺表继续(见类型注释)。 -WorkspaceShadowTable loadWorkspaceShadowTable(mjbase::workspace::IWorkspace& workspace, +WorkspaceShadowTable loadWorkspaceShadowTable(mjt::workspace::IWorkspace& workspace, const std::string& file = {}); ///@brief name 槽解析结果(接线层薄包装)。 @@ -57,7 +57,7 @@ bool resolveClassSlot(const WorkspaceShadowTable& slots, ///(classInfo 语义;exact-URI 消费面 patch_*/workspace_revert_class 用)。 ///raw 输入不经此函数(原样直通,契约不变)。 ///@return false 时 `error` 已填结构化 toolError。 -bool resourceUriOfResolved(mjbase::workspace::IWorkspace& workspace, +bool resourceUriOfResolved(mjt::workspace::IWorkspace& workspace, const std::string& internalName, std::string& uri, Json& error); @@ -98,7 +98,7 @@ bool resolveMethodSlots(const WorkspaceShadowTable& slots, ///错误(表外类如 java/lang/Object 的直通查询不受影响)。 ///@param field true 按字段池解析(xref target=field),false 按方法池。 ///@return false 时 `error` 已填结构化 toolError(含 diagnostics)。 -bool backfillMemberDescriptor(mjbase::workspace::IWorkspace& workspace, +bool backfillMemberDescriptor(mjt::workspace::IWorkspace& workspace, const std::string& ownerRaw, const std::string& memberName, bool field, @@ -108,7 +108,7 @@ bool backfillMemberDescriptor(mjbase::workspace::IWorkspace& workspace, ///@brief arguments 内 class 语义键(`class`)就地解析:Ref → 内部名; ///raw → verifyRaw 核表后原样。空值直通(workspace 既有 canonical 错误)。 ///@return false 时 `error` 已填结构化 toolError。 -bool resolveClassArgument(mjbase::workspace::IWorkspace& workspace, +bool resolveClassArgument(mjt::workspace::IWorkspace& workspace, const Json& arguments, const std::string& key, bool verifyRaw, @@ -120,7 +120,7 @@ bool resolveClassArgument(mjbase::workspace::IWorkspace& workspace, ///Ref → 内部名 → 精确资源 URI(exact-URI 消费面契约);raw → 按内部名核表 ///(miss → raw-not-found),命中后**原样透传**(URI 契约零漂移)。空值直通。 ///@return false 时 `error` 已填结构化 toolError。 -bool resolveClassUriArgument(mjbase::workspace::IWorkspace& workspace, +bool resolveClassUriArgument(mjt::workspace::IWorkspace& workspace, const Json& arguments, const std::string& key, std::string& out, @@ -131,7 +131,7 @@ bool resolveClassUriArgument(mjbase::workspace::IWorkspace& workspace, ///歧义路径同一呈现面)。name 不存在 / owner 不可解析 / descriptor 恰好命中 ///时返回 true(不适配——失败另有其因,走 canonical 错误)。 ///@return false 时 `error` 已填结构化 toolResult(含 diagnostics)。 -bool memberDescriptorMismatch(mjbase::workspace::IWorkspace& workspace, +bool memberDescriptorMismatch(mjt::workspace::IWorkspace& workspace, const std::string& ownerRaw, const std::string& memberName, const std::string& descriptor, diff --git a/mjt/mcp/test/mcp_parser_guard_test.cpp b/mjt/mcp/test/mcp_parser_guard_test.cpp index bc0c3e0..9000f0b 100644 --- a/mjt/mcp/test/mcp_parser_guard_test.cpp +++ b/mjt/mcp/test/mcp_parser_guard_test.cpp @@ -42,7 +42,7 @@ TEST(McpParserGuardTest, MutationRequestAcceptsNonNegativeIntegerRevision) //缺省:保留 kAnyRevision 语义。 auto absent = mutationRequest(Json::object()); - EXPECT_EQ(mjbase::workspace::kAnyRevision, absent.expectedRevision); + EXPECT_EQ(mjt::workspace::kAnyRevision, absent.expectedRevision); } TEST(McpParserGuardTest, ParseCursorRejectsNonStringCursor) diff --git a/mjt/mcp/test/mcp_tool_contract_test.cpp b/mjt/mcp/test/mcp_tool_contract_test.cpp index b5b7d39..260a70a 100644 --- a/mjt/mcp/test/mcp_tool_contract_test.cpp +++ b/mjt/mcp/test/mcp_tool_contract_test.cpp @@ -154,7 +154,7 @@ TEST(McpServerTest, ShapesCoarseToolSelectors) EXPECT_EQ(ca::usize{detail::kMaximumPageSize}, schemaFor("search").at("properties").at("limit").at("maximum")); EXPECT_TRUE(classQuery.at("properties").at("cursor").contains("description")); - EXPECT_EQ(((mjbase::workspace::kMaximumClassReplacementSize + 2) / 3) * 4, + EXPECT_EQ(((mjt::workspace::kMaximumClassReplacementSize + 2) / 3) * 4, editApply.at("properties").at("content_base64").at("maxLength")); const auto search = schemaFor("search"); EXPECT_EQ(Json::array({"classes", "members", "strings", "numbers", "instructions", @@ -286,7 +286,7 @@ TEST(McpServerTest, RejectsMaliciousToolArgumentsAndUnsafeExports) "edit_apply", ca::StatusCode::INVALID_ARGUMENT, "strict RFC 4648 base64"); const auto maximumBase64 = - ((mjbase::workspace::kMaximumClassReplacementSize + 2) / 3) * 4; + ((mjt::workspace::kMaximumClassReplacementSize + 2) / 3) * 4; expectDirectToolError(service.call("edit_apply", {{"project_id", projectId}, {"kind", "class_bytes"}, diff --git a/mjt/src/shared/jar_remapper.cpp b/mjt/src/shared/jar_remapper.cpp index e4ce685..53e3ed0 100644 --- a/mjt/src/shared/jar_remapper.cpp +++ b/mjt/src/shared/jar_remapper.cpp @@ -25,11 +25,11 @@ using mjbase::mapping::IntermediateMapping; ///i18n 文本 → WorkspaceErrorInfo(与 errStatus 同源的呈现面,错误通道换 ///mjbase workspace 的值语义)。 -mjbase::workspace::WorkspaceErrorInfo errInfo(mjbase::workspace::WorkspaceError code, +mjt::workspace::WorkspaceErrorInfo errInfo(mjt::workspace::WorkspaceError code, const char* key, std::initializer_list args = {}) { - mjbase::workspace::WorkspaceErrorInfo info; + mjt::workspace::WorkspaceErrorInfo info; info.code = code; info.message = shared::trfStr(key, args); return info; @@ -118,31 +118,31 @@ bool parsePolicy(const std::string& name, RemapPolicy& policy) return false; } -mjbase::workspace::WorkspaceErrorInfo buildPlan(const std::vector& classNames, +mjt::workspace::WorkspaceErrorInfo buildPlan(const std::vector& classNames, const std::string& format, const std::string& mappingText, const std::string& policyName, MemberRemapPlan& plan, RemapPolicy& policy) { if (format.empty() || mappingText.empty()) { - return errInfo(mjbase::workspace::WorkspaceError::InvalidArgument, + return errInfo(mjt::workspace::WorkspaceError::InvalidArgument, "mjt.shared.err.ws_remap_missing"); } auto registry = mjbase::mapping::MappingFormatRegistry::withStandardFormats(); const auto formatRef = ca::str::Utf8StringRef::from_cstr(format.c_str()); if (!registry.hasFormat(formatRef)) { - return errInfo(mjbase::workspace::WorkspaceError::InvalidArgument, + return errInfo(mjt::workspace::WorkspaceError::InvalidArgument, "mjt.shared.err.unknown_mapping_format", {format}); } if (!parsePolicy(policyName, policy)) { - return errInfo(mjbase::workspace::WorkspaceError::InvalidArgument, + return errInfo(mjt::workspace::WorkspaceError::InvalidArgument, "mjt.shared.err.unknown_remap_policy", {policyName}); } auto parsed = registry.parse( formatRef, ca::str::Utf8StringRef::from_data( reinterpret_cast(mappingText.data()), mappingText.size())); if (parsed.is_err()) { - return errInfo(mjbase::workspace::WorkspaceError::InvalidArgument, + return errInfo(mjt::workspace::WorkspaceError::InvalidArgument, "mjt.shared.err.parse_mapping_failed", {mappingErrorText(std::move(parsed).unwrap_err())}); } @@ -169,8 +169,8 @@ std::vector classNames(const mjbase::model::ResourceContainer& cont return names; } -std::vector classNames(const mjbase::workspace::IWorkspace& workspace, - mjbase::workspace::WorkspaceErrorInfo& status) +std::vector classNames(const mjt::workspace::IWorkspace& workspace, + mjt::workspace::WorkspaceErrorInfo& status) { std::vector names; auto classes = workspace.listClasses(); @@ -244,20 +244,20 @@ ca::StatusOr remapJar(const JarRemapRequest& request) } } -mjbase::workspace::WorkspaceErrorInfo buildWorkspaceRemapPlan( - mjbase::workspace::IWorkspace& workspace, const WorkspaceRemapRequest& request, +mjt::workspace::WorkspaceErrorInfo buildWorkspaceRemapPlan( + mjt::workspace::IWorkspace& workspace, const WorkspaceRemapRequest& request, MemberRemapPlan& plan, RemapPolicy& policy) { - mjbase::workspace::WorkspaceErrorInfo status; + mjt::workspace::WorkspaceErrorInfo status; auto names = classNames(workspace, status); if (status.is_err()) return status; return buildPlan(names, request.format, request.mappingText, request.policy, plan, policy); } -mjbase::workspace::ImpactAnalysis analyzeWorkspaceRemap( - mjbase::workspace::IWorkspace& workspace, const WorkspaceRemapRequest& request) +mjt::workspace::ImpactAnalysis analyzeWorkspaceRemap( + mjt::workspace::IWorkspace& workspace, const WorkspaceRemapRequest& request) { - mjbase::workspace::ImpactAnalysis result; + mjt::workspace::ImpactAnalysis result; MemberRemapPlan plan; RemapPolicy policy; auto status = buildWorkspaceRemapPlan(workspace, request, plan, policy); @@ -268,15 +268,15 @@ mjbase::workspace::ImpactAnalysis analyzeWorkspaceRemap( return workspace.analyzeRemap(plan, policy); } -mjbase::workspace::MutationResult applyWorkspaceRemap( - mjbase::workspace::IWorkspace& workspace, const WorkspaceRemapRequest& request, - const mjbase::workspace::MutationRequest& mutation) +mjt::workspace::MutationResult applyWorkspaceRemap( + mjt::workspace::IWorkspace& workspace, const WorkspaceRemapRequest& request, + const mjt::workspace::MutationRequest& mutation) { MemberRemapPlan plan; RemapPolicy policy; auto status = buildWorkspaceRemapPlan(workspace, request, plan, policy); if (status.is_err()) { - mjbase::workspace::MutationResult result; + mjt::workspace::MutationResult result; result.status = std::move(status); result.revision = workspace.revision(); result.exportable = workspace.canExport(); diff --git a/mjt/src/shared/jar_remapper.hpp b/mjt/src/shared/jar_remapper.hpp index 9a0472c..76967c0 100644 --- a/mjt/src/shared/jar_remapper.hpp +++ b/mjt/src/shared/jar_remapper.hpp @@ -3,7 +3,7 @@ #include #include "libca/core/status.hpp" -#include "mjbase/workspace/i_workspace.hpp" +#include "workspace/i_workspace.hpp" namespace mjt { @@ -37,17 +37,17 @@ struct WorkspaceRemapRequest final ///@brief 将 mapping 文本解析为可供单一 Workspace 候选事务复用的 remap 计划。 ///@note 输出计划必须在使用它的 Workspace 调用返回前保持有效。 -mjbase::workspace::WorkspaceErrorInfo buildWorkspaceRemapPlan( - mjbase::workspace::IWorkspace& workspace, const WorkspaceRemapRequest& request, +mjt::workspace::WorkspaceErrorInfo buildWorkspaceRemapPlan( + mjt::workspace::IWorkspace& workspace, const WorkspaceRemapRequest& request, mjbase::asm_remapper::MemberRemapPlan& plan, mjbase::asm_remapper::RemapPolicy& policy); ///@brief 返回 mapping 应用后的引用影响、验证诊断和字节 diff,不提交工作区。 -mjbase::workspace::ImpactAnalysis analyzeWorkspaceRemap( - mjbase::workspace::IWorkspace& workspace, const WorkspaceRemapRequest& request); +mjt::workspace::ImpactAnalysis analyzeWorkspaceRemap( + mjt::workspace::IWorkspace& workspace, const WorkspaceRemapRequest& request); ///@brief 原子应用 mapping 文本;dryRun 时返回与分析一致的 MutationResult。 -mjbase::workspace::MutationResult applyWorkspaceRemap( - mjbase::workspace::IWorkspace& workspace, const WorkspaceRemapRequest& request, - const mjbase::workspace::MutationRequest& mutation = {}); +mjt::workspace::MutationResult applyWorkspaceRemap( + mjt::workspace::IWorkspace& workspace, const WorkspaceRemapRequest& request, + const mjt::workspace::MutationRequest& mutation = {}); } //namespace mjt diff --git a/mjt/src/shared/jar_workspace.hpp b/mjt/src/shared/jar_workspace.hpp index 6a6e051..6b384ec 100644 --- a/mjt/src/shared/jar_workspace.hpp +++ b/mjt/src/shared/jar_workspace.hpp @@ -1,6 +1,6 @@ #pragma once -#include "mjbase/workspace/workspace.hpp" +#include "workspace/workspace.hpp" namespace mjt { @@ -9,7 +9,7 @@ namespace mjt { ///mjt 当前无扩展语义,直接继承 mjbase 基础实现;业务语义(若有)在 mjt 侧 ///落位,不进 mjbase IWorkspace。所有 mjt 调用点面向 `IWorkspace` 编程, ///实例化点统一使用本类。 -class JarWorkspace : public mjbase::workspace::WorkspaceBase +class JarWorkspace : public mjt::workspace::WorkspaceBase { public: using WorkspaceBase::WorkspaceBase; diff --git a/mjt/src/shared/status_convert.hpp b/mjt/src/shared/status_convert.hpp index 1603a5b..5d845d7 100644 --- a/mjt/src/shared/status_convert.hpp +++ b/mjt/src/shared/status_convert.hpp @@ -9,36 +9,36 @@ #include #include "libca/core/status.hpp" -#include "mjbase/workspace/workspace_types.hpp" +#include "workspace/workspace_types.hpp" namespace mjt::shared { ///WorkspaceError → ca::StatusCode(逐项对齐;Ok/Internal 兜底 INTERNAL)。 -inline ca::StatusCode statusCodeOf(mjbase::workspace::WorkspaceError code) +inline ca::StatusCode statusCodeOf(mjt::workspace::WorkspaceError code) { switch (code) { - case mjbase::workspace::WorkspaceError::InvalidArgument: + case mjt::workspace::WorkspaceError::InvalidArgument: return ca::StatusCode::INVALID_ARGUMENT; - case mjbase::workspace::WorkspaceError::NotFound: return ca::StatusCode::NOT_FOUND; - case mjbase::workspace::WorkspaceError::AlreadyExists: + case mjt::workspace::WorkspaceError::NotFound: return ca::StatusCode::NOT_FOUND; + case mjt::workspace::WorkspaceError::AlreadyExists: return ca::StatusCode::ALREADY_EXISTS; - case mjbase::workspace::WorkspaceError::FailedPrecondition: + case mjt::workspace::WorkspaceError::FailedPrecondition: return ca::StatusCode::FAILED_PRECONDITION; - case mjbase::workspace::WorkspaceError::PermissionDenied: + case mjt::workspace::WorkspaceError::PermissionDenied: return ca::StatusCode::PERMISSION_DENIED; - case mjbase::workspace::WorkspaceError::Aborted: return ca::StatusCode::ABORTED; - case mjbase::workspace::WorkspaceError::DataLoss: return ca::StatusCode::DATA_LOSS; - case mjbase::workspace::WorkspaceError::Unimplemented: + case mjt::workspace::WorkspaceError::Aborted: return ca::StatusCode::ABORTED; + case mjt::workspace::WorkspaceError::DataLoss: return ca::StatusCode::DATA_LOSS; + case mjt::workspace::WorkspaceError::Unimplemented: return ca::StatusCode::UNIMPLEMENTED; - case mjbase::workspace::WorkspaceError::Ok: - case mjbase::workspace::WorkspaceError::Internal: break; + case mjt::workspace::WorkspaceError::Ok: + case mjt::workspace::WorkspaceError::Internal: break; } return ca::StatusCode::INTERNAL; } ///WorkspaceErrorInfo → ca::Status(message 文本原样透传;OK 值映射回 ca::OkStatus, ///否则成功路径会被误判为失败——commit 成功即被当成失败上报)。 -inline ca::Status toCaStatus(const mjbase::workspace::WorkspaceErrorInfo& info) +inline ca::Status toCaStatus(const mjt::workspace::WorkspaceErrorInfo& info) { if (!info.is_err()) return ca::OkStatus(); return ca::ErrStatus(statusCodeOf(info.code), info.message); @@ -46,34 +46,34 @@ inline ca::Status toCaStatus(const mjbase::workspace::WorkspaceErrorInfo& info) ///ca::StatusCode → WorkspaceError(反向对齐;CANCELLED 并入 Aborted, ///DEADLINE_EXCEEDED/UNAVAILABLE 等无对应值的码兜底 Internal)。 -inline mjbase::workspace::WorkspaceError workspaceErrorOf(ca::StatusCode code) +inline mjt::workspace::WorkspaceError workspaceErrorOf(ca::StatusCode code) { switch (code) { case ca::StatusCode::INVALID_ARGUMENT: - return mjbase::workspace::WorkspaceError::InvalidArgument; - case ca::StatusCode::NOT_FOUND: return mjbase::workspace::WorkspaceError::NotFound; + return mjt::workspace::WorkspaceError::InvalidArgument; + case ca::StatusCode::NOT_FOUND: return mjt::workspace::WorkspaceError::NotFound; case ca::StatusCode::ALREADY_EXISTS: - return mjbase::workspace::WorkspaceError::AlreadyExists; + return mjt::workspace::WorkspaceError::AlreadyExists; case ca::StatusCode::FAILED_PRECONDITION: - return mjbase::workspace::WorkspaceError::FailedPrecondition; + return mjt::workspace::WorkspaceError::FailedPrecondition; case ca::StatusCode::PERMISSION_DENIED: - return mjbase::workspace::WorkspaceError::PermissionDenied; + return mjt::workspace::WorkspaceError::PermissionDenied; case ca::StatusCode::ABORTED: - case ca::StatusCode::CANCELLED: return mjbase::workspace::WorkspaceError::Aborted; - case ca::StatusCode::DATA_LOSS: return mjbase::workspace::WorkspaceError::DataLoss; + case ca::StatusCode::CANCELLED: return mjt::workspace::WorkspaceError::Aborted; + case ca::StatusCode::DATA_LOSS: return mjt::workspace::WorkspaceError::DataLoss; case ca::StatusCode::UNIMPLEMENTED: - return mjbase::workspace::WorkspaceError::Unimplemented; + return mjt::workspace::WorkspaceError::Unimplemented; case ca::StatusCode::OK: default: break; } - return mjbase::workspace::WorkspaceError::Internal; + return mjt::workspace::WorkspaceError::Internal; } ///ca::Status → WorkspaceErrorInfo(message 文本原样透传;mjt 自有 API 的 ///ca::Status 错误流回 mjbase 候选事务通道时使用。OK 值映射回默认 Ok info)。 -inline mjbase::workspace::WorkspaceErrorInfo fromCaStatus(const ca::Status& status) +inline mjt::workspace::WorkspaceErrorInfo fromCaStatus(const ca::Status& status) { - mjbase::workspace::WorkspaceErrorInfo info; + mjt::workspace::WorkspaceErrorInfo info; if (!status.is_err()) return info; info.code = workspaceErrorOf(status.code()); info.message = status.message(); diff --git a/mjt/src/workspace/class_mutation_service.cpp b/mjt/src/workspace/class_mutation_service.cpp new file mode 100644 index 0000000..19f5a93 --- /dev/null +++ b/mjt/src/workspace/class_mutation_service.cpp @@ -0,0 +1,1551 @@ +#include "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 "classfile_support.hpp" + +namespace mjt::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 mjt::workspace diff --git a/mjt/src/workspace/class_mutation_service.hpp b/mjt/src/workspace/class_mutation_service.hpp new file mode 100644 index 0000000..b0daedd --- /dev/null +++ b/mjt/src/workspace/class_mutation_service.hpp @@ -0,0 +1,270 @@ +#pragma once + +#include +#include + +#include "libca/core/datatype.hpp" + +#include "mjbase/model/resource_container.hpp" +#include "mjbase/model/resource_model.hpp" +#include "workspace_types.hpp" + +namespace mjt::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 mjt::workspace diff --git a/mjt/src/workspace/classfile_support.cpp b/mjt/src/workspace/classfile_support.cpp new file mode 100644 index 0000000..2fe7173 --- /dev/null +++ b/mjt/src/workspace/classfile_support.cpp @@ -0,0 +1,308 @@ +#include "classfile_support.hpp" + +#include +#include +#include + +namespace mjt::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 mjt::workspace::support diff --git a/mjt/src/workspace/classfile_support.hpp b/mjt/src/workspace/classfile_support.hpp new file mode 100644 index 0000000..9feedf5 --- /dev/null +++ b/mjt/src/workspace/classfile_support.hpp @@ -0,0 +1,223 @@ +/// @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 "workspace_types.hpp" + +namespace mjt::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 mjt::workspace::support diff --git a/mjt/src/workspace/i_workspace.hpp b/mjt/src/workspace/i_workspace.hpp new file mode 100644 index 0000000..88cf7b2 --- /dev/null +++ b/mjt/src/workspace/i_workspace.hpp @@ -0,0 +1,304 @@ +#pragma once + +#include "mjbase/model/container_stack.hpp" +#include "class_mutation_service.hpp" +#include "workspace_types.hpp" + +namespace mjt::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 mjt::workspace diff --git a/mjt/src/workspace/reference_scan.cpp b/mjt/src/workspace/reference_scan.cpp new file mode 100644 index 0000000..9af6dea --- /dev/null +++ b/mjt/src/workspace/reference_scan.cpp @@ -0,0 +1,250 @@ +#include "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 "classfile_support.hpp" + +namespace mjt::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 mjt::workspace diff --git a/mjt/src/workspace/reference_scan.hpp b/mjt/src/workspace/reference_scan.hpp new file mode 100644 index 0000000..fa99221 --- /dev/null +++ b/mjt/src/workspace/reference_scan.hpp @@ -0,0 +1,128 @@ +/// @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 "workspace_types.hpp" + +namespace mjt::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 mjt::workspace diff --git a/mjt/src/workspace/summary_scan.cpp b/mjt/src/workspace/summary_scan.cpp new file mode 100644 index 0000000..296924d --- /dev/null +++ b/mjt/src/workspace/summary_scan.cpp @@ -0,0 +1,32 @@ +#include "summary_scan.hpp" + +#include "jbc/core/reader.hpp" + +namespace mjt::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 mjt::workspace diff --git a/mjt/src/workspace/summary_scan.hpp b/mjt/src/workspace/summary_scan.hpp new file mode 100644 index 0000000..efd53df --- /dev/null +++ b/mjt/src/workspace/summary_scan.hpp @@ -0,0 +1,25 @@ +/// @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 "workspace_types.hpp" + +namespace mjt::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 mjt::workspace diff --git a/mjt/src/workspace/workspace.cpp b/mjt/src/workspace/workspace.cpp new file mode 100644 index 0000000..b724fcf --- /dev/null +++ b/mjt/src/workspace/workspace.cpp @@ -0,0 +1,4806 @@ +#include "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 "class_mutation_service.hpp" +#include "classfile_support.hpp" +#include "reference_scan.hpp" +#include "summary_scan.hpp" + +namespace mjt::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 mjt::workspace diff --git a/mjt/src/workspace/workspace.hpp b/mjt/src/workspace/workspace.hpp new file mode 100644 index 0000000..f432920 --- /dev/null +++ b/mjt/src/workspace/workspace.hpp @@ -0,0 +1,464 @@ +#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 "i_workspace.hpp" +#include "workspace_types.hpp" + +namespace mjbase { + +class KeyPool; ///< 驻留池(keys.hpp;此处仅前置声明) + +namespace hierarchy { +class ClassHierarchy; ///< 层次图(hierarchy 模块;此处仅前置声明) +} // namespace hierarchy + +} // namespace mjbase + +// 本模块(mjt::workspace)自 mjbase workspace 引擎移交而来:兄弟域仍走 ::mjbase, +// 经 workspace_types.hpp 的别名以原相对形引用,引擎正文零改动。 +namespace mjt::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 mjt::workspace diff --git a/mjt/src/workspace/workspace_types.hpp b/mjt/src/workspace/workspace_types.hpp new file mode 100644 index 0000000..02dd9f7 --- /dev/null +++ b/mjt/src/workspace/workspace_types.hpp @@ -0,0 +1,714 @@ +#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 头文件后续重入合流), +// 供下方别名绑定;别名:引擎正文以相对形(model::/jasm::/…)引用 mjbase 域, +// 移交 mjt 后经显式别名保持原相对形可解析——正文零改动,命名空间属主为 mjt。 +namespace mjbase { +namespace analysis {} +namespace hierarchy {} +namespace mapping {} +namespace model {} +namespace project {} +namespace spi {} +} // namespace mjbase + +namespace mjt::workspace { + +namespace analysis = ::mjbase::analysis; +namespace asm_remapper = ::mjbase::asm_remapper; +namespace hierarchy = ::mjbase::hierarchy; +namespace jasm = ::mjbase::jasm; +namespace mapping = ::mjbase::mapping; +namespace model = ::mjbase::model; +namespace project = ::mjbase::project; +namespace spi = ::mjbase::spi; + +/// @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 mjt::workspace diff --git a/mjt/xmake.lua b/mjt/xmake.lua index ba34bb6..0861a21 100644 --- a/mjt/xmake.lua +++ b/mjt/xmake.lua @@ -13,6 +13,9 @@ target("mjt_shared") add_includedirs("src", {public = true}) add_files("src/shared/*.cpp") + -- workspace 引擎(自 mjbase workspace 模块移交,ns mjt::workspace):编辑 + -- 台账/类变更/引用·摘要扫描,MCP 工具面与 pysdk 绑定共用的底座件。 + add_files("src/workspace/**.cpp") add_files("src/shared/translations/*.lang", {rules = "libca.i18n.embed-lang"}) target("mjt_mcp_core") @@ -70,6 +73,27 @@ target("mjt-mcp") set_rundir("$(projectdir)") if has_config("with_tests") then + target("mjt_workspace_unittest") + set_kind("binary") + set_group("mjt/test") + set_default(false) + + add_deps("mjt_shared", "test_helper") + add_packages("gtest") + -- xmake 3.x 不把静态 target 依赖传进 binary 链接:显式给全闭包,按 + -- 「消费者在前、被依赖在后」排序(与 mjt_mcp_unittest 同款)。 + mjt_binary_link_closure("mjt_shared", "mjbase") + mjt_binary_link_closure("libjhasm", "libjclassfile", "libjstr", "libjbc", "libjzip", "test_helper", "libjdk_finder") + add_files("test/workspace/*.cpp") + set_rundir("$(projectdir)") + + if is_plat("windows") then + add_cxflags("/utf-8", {tools = "cl"}) + end + if is_plat("linux") then + add_syslinks("pthread") + end + target("mjt_mcp_unittest") set_kind("binary") set_group("mjt/test")