From 157c5c7b37fda739b3843d6d24706c5f721606d9 Mon Sep 17 00:00:00 2001 From: Canrad <1517807724@qq.com> Date: Thu, 29 Oct 2026 22:04:58 +0800 Subject: [PATCH] =?UTF-8?q?refactor(consumer):=20consumer=20=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E9=80=80=E5=BD=B9=E2=80=94=E2=80=94=E5=85=A8=E4=BB=93?= =?UTF-8?q?=E9=9B=B6=E6=B6=88=E8=B4=B9=E6=96=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IClassSource 协议 + SummaryScanner 骨架扫描(E 系 E3 面,14 用例): 实测 mjt 从未 include,唯一消费面是自测,属按图纸预制的抽象 (libjbc design §9 草案落地件)。按仓规「没有消费方的模块不进仓」 整体移除;协议面待真实消费方出现时按需求重建。dev_plan E 系条目 同步标注退役申报。 套件 1919 用例 1916 绿 + 3 既有 POSIX 跳过(1933 − 14 逐位对账)。 --- mjbase/dev_plan.md | 7 +- mjbase/src/mjbase/consumer/class_source.cpp | 130 --------- mjbase/src/mjbase/consumer/class_source.hpp | 152 ----------- .../src/mjbase/consumer/summary_scanner.cpp | 101 ------- .../src/mjbase/consumer/summary_scanner.hpp | 81 ------ .../unittest/consumer/class_source_test.cpp | 239 ----------------- .../unittest/consumer/summary_parity_test.cpp | 246 ------------------ mjbase/xmake.lua | 2 - 8 files changed, 5 insertions(+), 953 deletions(-) delete mode 100644 mjbase/src/mjbase/consumer/class_source.cpp delete mode 100644 mjbase/src/mjbase/consumer/class_source.hpp delete mode 100644 mjbase/src/mjbase/consumer/summary_scanner.cpp delete mode 100644 mjbase/src/mjbase/consumer/summary_scanner.hpp delete mode 100644 mjbase/test/unittest/consumer/class_source_test.cpp delete mode 100644 mjbase/test/unittest/consumer/summary_parity_test.cpp diff --git a/mjbase/dev_plan.md b/mjbase/dev_plan.md index 79a6e00..983b0b1 100644 --- a/mjbase/dev_plan.md +++ b/mjbase/dev_plan.md @@ -734,8 +734,11 @@ mjt-deobf 侧尚在 mbase 栈消费的 opt pass 逐件裁决;"结构性吸收" member_remapper 两处换装 + cp_refs.hpp 转发头(mjbase PR #74; 行为收紧:kind 域外从「误走方法分支」变「跳过」,readClass 读闸下 畸形类不可达,remapper 侧污染接缝真红→绿用例钉死)。 -- E 系消费者对接(module-map 第八批):[x] E3 类字节供给面 + 骨架扫描面 - (consumer/class_source + consumer/summary_scanner,14 用例全绿); +- E 系消费者对接(module-map 第八批):[x→退役] E3 类字节供给面 + 骨架扫描面 + (consumer/class_source + consumer/summary_scanner,14 用例全绿; + 2026-10-29 退役:全仓零消费方——mjt 从未 include,唯一消费面是自测; + 按仓规「没有消费方的模块不进仓」整体移除,IClassSource 协议待真实 + 消费方出现时按需求重建); [x] E2 ChangeSet 消费方接线(2026-10-14,libjbc design §35):ReferenceIndex `applyChangeSet` 按 ChangeSet 局部重建(方法粒度精确补丁 + 字段/类级事实面 重扫),`PassPipelineOptions::changeSink` 在 GlobalToClassPassAdaptor 类级 diff --git a/mjbase/src/mjbase/consumer/class_source.cpp b/mjbase/src/mjbase/consumer/class_source.cpp deleted file mode 100644 index 12108b6..0000000 --- a/mjbase/src/mjbase/consumer/class_source.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/// @file class_source.cpp -/// @brief E3 类源实现:内存 map 版与 ResourceContainer 适配版。 -#include "mjbase/consumer/class_source.hpp" - -#include - -#include "jbc/core/error.hpp" -#include "jbc/core/reader.hpp" -#include "libca/core/tag_cast.hpp" - -namespace mjbase::consumer { - -const char* to_string(ErrorCode code) noexcept -{ - switch (code) { - case ErrorCode::NotFound: return "NotFound"; - case ErrorCode::BadClassFile: return "BadClassFile"; - } - return "?"; -} - -namespace { - -/// @brief 名字字典序比较(经驻留池展开;稳定排序的键函数)。 -struct NameOrderLess -{ - const KeyPool* pool; - bool operator()(ca::u32 lhs, ca::u32 rhs) const noexcept - { - return pool->str(lhs) < pool->str(rhs); - } -}; - -/// @brief 纯扫描读选项:不保留源字节副本(读侧只出骨架事实)。 -jbc::core::ReadOptions scanReadOptions() -{ - jbc::core::ReadOptions options; - options.preserveSourceBytes = false; - return options; -} - -} // namespace - -// ── MemoryClassSource ──────────────────────────────────────────────────────── - -void MemoryClassSource::add(std::string_view internalName, std::vector bytes) -{ - const ca::u32 id = pool_.intern(internalName); - classes_[id] = std::move(bytes); - const auto it = std::lower_bound(sortedIds_.begin(), sortedIds_.end(), id, - NameOrderLess{&pool_}); - if (it != sortedIds_.end() && *it == id) return; - sortedIds_.insert(it, id); -} - -ConsumerResult MemoryClassSource::bytes(std::string_view internalName) const -{ - const ca::u32 id = pool_.intern(internalName); - const auto it = classes_.find(id); - if (it == classes_.end()) { - return ca::core::Err(ConsumerError{ - ErrorCode::NotFound, "class source: not found: " + std::string(internalName)}); - } - return ca::core::Ok(jbc::core::ByteView{it->second}); -} - -void MemoryClassSource::listClasses(std::vector& out) const -{ - out.reserve(out.size() + sortedIds_.size()); - for (const ca::u32 id : sortedIds_) out.push_back(pool_.str(id)); -} - -// ── ContainerClassSource ───────────────────────────────────────────────────── - -ConsumerResult ContainerClassSource::make( - const model::ResourceContainer& container) -{ - ContainerClassSource source; - ConsumerError failure{}; - bool failed = false; - container.visitResources([&](const std::string_view uri, const model::IResource& resource) { - if (failed) return; - const auto* cls = ca::core::tag_cast::dyn_cast(&resource); - if (cls == nullptr) return; // 非 Class 资源忽略 - - auto read = jbc::core::readClass(jbc::core::ByteView{cls->get()}, scanReadOptions()); - if (read.is_err()) { - failed = true; - failure.code = ErrorCode::BadClassFile; - failure.message = "class source: read " + std::string{uri} + ": " + - jbc::core::to_string(std::move(read).unwrap_err()); - return; - } - const std::string_view name = std::move(read).unwrap().file->thisName(); - - const ca::u32 id = source.pool_.intern(name); - if (!source.resources_.emplace(id, cls).second) { - // first-wins 保留首条(JVM 类加载语义);重复记录供调用方告警。 - source.duplicates_.push_back(source.pool_.str(id)); - return; - } - const auto pos = std::lower_bound(source.sortedIds_.begin(), source.sortedIds_.end(), id, - NameOrderLess{&source.pool_}); - source.sortedIds_.insert(pos, id); - }); - if (failed) return ca::core::Err(std::move(failure)); - - // 重复项按名字序报告(与枚举序一致,跨容器确定性)。 - std::sort(source.duplicates_.begin(), source.duplicates_.end()); - return ca::core::Ok(std::move(source)); -} - -ConsumerResult ContainerClassSource::bytes(std::string_view internalName) const -{ - const ca::u32 id = pool_.intern(internalName); - const auto it = resources_.find(id); - if (it == resources_.end()) { - return ca::core::Err(ConsumerError{ - ErrorCode::NotFound, "class source: not found: " + std::string(internalName)}); - } - return ca::core::Ok(jbc::core::ByteView{it->second->get()}); -} - -void ContainerClassSource::listClasses(std::vector& out) const -{ - out.reserve(out.size() + sortedIds_.size()); - for (const ca::u32 id : sortedIds_) out.push_back(pool_.str(id)); -} - -} // namespace mjbase::consumer diff --git a/mjbase/src/mjbase/consumer/class_source.hpp b/mjbase/src/mjbase/consumer/class_source.hpp deleted file mode 100644 index 9a806fa..0000000 --- a/mjbase/src/mjbase/consumer/class_source.hpp +++ /dev/null @@ -1,152 +0,0 @@ -/// @file class_source.hpp -/// @brief E3 类源抽象:IClassSource 协议 + 内存/容器两个实现。 -/// -/// 消费者对接协议(E 系)的类字节供给面:产品线(开源 mjt 等)经本面按 -/// 内部名取 class 字节、枚举类集合,不直接触碰 ResourceContainer/Jar 细节。 -/// 设计承自 libjbc design.md §9 的 IClassSource 草案,按 mjbase 底座重定型: -/// 键是 JVM 内部名字符串(协议面的调用方还没有 KeyPool),错误走 Result 值 -/// 语义,fail-closed——按名取类缺名显式 Err(NotFound),不静默空。 -/// -/// 两个实现: -/// - MemoryClassSource:进程内 map(测试/内联装配); -/// - ContainerClassSource:ResourceContainer 适配(目录/JAR 经 model 批的 -/// 导入器装载后即为容器,本类是其协议面投影)。 -/// -/// 借用纪律:bytes/listClasses 返回的视图借用来源自持存储,来源存活期内 -/// 有效;ContainerClassSource 另要求容器在来源存活期内结构冻结(不增删/ -/// 替换资源),否则字节视图可能悬垂。 -#pragma once - -#include -#include -#include -#include - -#include "libca/core/result.hpp" - -#include "jbc/core/bytes.hpp" - -#include "mjbase/keys.hpp" -#include "mjbase/model/resource_container.hpp" - -namespace mjbase::consumer { - -/// @brief consumer 模块错误码。 -enum class ErrorCode : ca::u8 -{ - NotFound, ///< 按名取类:名字不在源内(fail-closed,不静默空) - BadClassFile, ///< class 字节解析失败(libjbc 读侧 Err,消息带来源标识) -}; - -/// @brief 错误码 → 短名(断言输出/日志用)。 -const char* to_string(ErrorCode code) noexcept; - -/// @brief consumer 模块错误值:码 + 消息文本(模块边界错误形态,消息落 -/// std::string——与 workspace::WorkspaceErrorInfo 同型,mjbase 总约定 -/// 中「std::string 只出现在输出/异常文本等外部边界」的豁免位)。 -/// 本值只经 Result 的 Err 通道出现,不承载 Ok 态。 -struct ConsumerError -{ - ErrorCode code = ErrorCode::NotFound; - std::string message; -}; - -/// @brief consumer 模块 Result 别名。 -template -using ConsumerResult = ca::core::Result; - -/// @brief 类字节供给抽象(E3 协议面)。 -/// -/// 实现保证:类名集合与字节内容在来源存活期内稳定(不随查询变化); -/// 枚举序恒为内部名字典序(确定性,与底层容器遍历序解耦)。 -class IClassSource -{ -public: - virtual ~IClassSource() = default; - - /// @brief 按内部名取 class 字节。 - /// - /// 缺名返回 Err(NotFound)(fail-closed:调用方显式决定如何处理缺失, - /// 库不代为跳过)。视图借用来源自持字节,来源存活期内有效。 - virtual ConsumerResult bytes(std::string_view internalName) const = 0; - - /// @brief 已知类名数。 - virtual ca::usize classCount() const noexcept = 0; - - /// @brief 枚举全部类名(内部名字典序,追加到 out)。 - /// - /// 视图借用来源自持文本,来源存活期内有效。 - virtual void listClasses(std::vector& out) const = 0; -}; - -/// @brief 内存 map 版类源:内部名 → class 字节(进程内装配/测试夹具用)。 -/// -/// add 为 upsert 语义(同名覆盖既有字节);枚举序恒为名字字典序。 -/// move-only(池驻留文本随源存活,复制无意义)。 -class MemoryClassSource final : public IClassSource -{ -public: - MemoryClassSource() = default; - MemoryClassSource(const MemoryClassSource&) = delete; - MemoryClassSource& operator=(const MemoryClassSource&) = delete; - MemoryClassSource(MemoryClassSource&&) = default; - MemoryClassSource& operator=(MemoryClassSource&&) = default; - - /// @brief 插入或覆盖一个类(视图入参,字节与名字均转自有存储)。 - void add(std::string_view internalName, std::vector bytes); - - ConsumerResult bytes(std::string_view internalName) const override; - ca::usize classCount() const noexcept override { return classes_.size(); } - void listClasses(std::vector& out) const override; - -private: - /// 键为 KeyPool 驻留 id(名字驻留作业池纪律);map 按 id 的字典序排列 - /// 由 sortedIds_ 显式承载,map 只做 id 寻址。 - std::map> classes_; - std::vector sortedIds_; ///< 名字字典序缓存(add 后重建) - /// 查询入参按名 intern 进本池(内容寻址、幂等),故 pool 为 mutable - /// (与 mjbase::reference::ReferenceIndex 同款纪律)。 - mutable KeyPool pool_; -}; - -/// @brief ResourceContainer 适配版类源(目录/JAR 容器投影)。 -/// -/// make 时逐 ClassResource 解析内部名(libjbc 骨架读): -/// - 任一 class 资源解析失败 → Err(BadClassFile)(fail-closed:源若带病 -/// 构建,后续按名取类会静默缺类)。消费方可先经 sanitize 闸门过滤畸形 -/// 资源再建源。 -/// - 同一内部名出现多次(不同 URI)按 first-wins 保留首条(JVM 类加载 -/// 语义,与容器 addResource 的 URI first-wins 同型),重复项记录在 -/// duplicateNames()。 -/// - 非 Class 资源(Manifest/Unknown)忽略。 -class ContainerClassSource final : public IClassSource -{ -public: - ContainerClassSource(ContainerClassSource&&) = default; - ContainerClassSource& operator=(ContainerClassSource&&) = default; - - /// @brief 从容器构建类源;container 必须在本源存活期内结构冻结。 - static ConsumerResult make(const model::ResourceContainer& container); - - /// @brief 构建期发现的重复内部名(first-wins 保留首条),名字序。 - /// - /// 视图借用源自持池,源存活期内有效。与容器 duplicateResourceUris() - /// (URI 级)互补:本表是解析后的内部名级重复。 - const std::vector& duplicateNames() const noexcept { return duplicates_; } - - ConsumerResult bytes(std::string_view internalName) const override; - ca::usize classCount() const noexcept override { return resources_.size(); } - void listClasses(std::vector& out) const override; - -private: - ContainerClassSource() = default; - - /// 查询入参按名 intern 进本池(内容寻址、幂等),故 pool 为 mutable - /// (与 mjbase::reference::ReferenceIndex 同款纪律)。 - mutable KeyPool pool_; - std::map resources_; ///< 内部名 id → 资源(借用) - std::vector sortedIds_; ///< 名字字典序缓存 - std::vector duplicates_; -}; - -} // namespace mjbase::consumer diff --git a/mjbase/src/mjbase/consumer/summary_scanner.cpp b/mjbase/src/mjbase/consumer/summary_scanner.cpp deleted file mode 100644 index 24ce410..0000000 --- a/mjbase/src/mjbase/consumer/summary_scanner.cpp +++ /dev/null @@ -1,101 +0,0 @@ -/// @file summary_scanner.cpp -/// @brief SummaryScanner 实现:libjbc core 骨架 → ClassSummary。 -#include "mjbase/consumer/summary_scanner.hpp" - -#include "jbc/core/class_file.hpp" -#include "jbc/core/error.hpp" -#include "jbc/core/reader.hpp" - -namespace mjbase::consumer { -namespace { - -/// @brief 纯扫描读选项:不保留源字节副本。 -jbc::core::ReadOptions scanReadOptions() -{ - jbc::core::ReadOptions options; - options.preserveSourceBytes = false; - return options; -} - -/// @brief 成员属性表中是否存在名为 name 的槽(hasCode 判定;不解码属性体)。 -bool hasAttrNamed(const jbc::core::ClassFile& cf, const std::vector& attrs, - std::string_view name) -{ - for (const jbc::core::AttrSlot& slot : attrs) { - if (cf.utf8(slot.nameCp) == name) return true; - } - return false; -} - -} // namespace - -ConsumerResult SummaryScanner::scan(jbc::core::ByteView bytes, KeyPool& pool) const -{ - auto read = jbc::core::readClass(bytes, scanReadOptions()); - if (read.is_err()) { - return ca::core::Err(ConsumerError{ - ErrorCode::BadClassFile, - "summary scan: " + std::string{jbc::core::to_string(std::move(read).unwrap_err())}}); - } - jbc::core::ReadResult parsed = std::move(read).unwrap(); - const jbc::core::ClassFile& cf = *parsed.file; - - ClassSummary summary; - summary.access = cf.access; - summary.version = (static_cast(cf.minor) << 16) | static_cast(cf.major); - summary.self = classKeyOf(pool, cf.thisName()); - - // 无父类(Object/module-info):读侧物化空串,映射回 invalid 键。 - const std::string_view superName = cf.superName(); - summary.super = superName.empty() ? ClassKey{} : classKeyOf(pool, superName); - - summary.interfaces.reserve(cf.interfaces.size()); - for (const ca::u16 iface : cf.interfaces) { - summary.interfaces.push_back(classKeyOf(pool, cf.className(iface))); - } - - summary.methods.reserve(cf.methods.size()); - for (const jbc::core::MemberInfo& m : cf.methods) { - SummaryMethod row; - row.name = pool.intern(cf.nameOf(m)); - row.desc = pool.intern(cf.descOf(m)); - row.access = m.access; - row.hasCode = hasAttrNamed(cf, m.attrs, "Code"); - summary.methods.push_back(row); - } - - summary.fields.reserve(cf.fields.size()); - for (const jbc::core::MemberInfo& f : cf.fields) { - SummaryField row; - row.name = pool.intern(cf.nameOf(f)); - row.desc = pool.intern(cf.descOf(f)); - row.access = f.access; - summary.fields.push_back(row); - } - return ca::core::Ok(std::move(summary)); -} - -ConsumerResult> SummaryScanner::scanSource(const IClassSource& source, - KeyPool& pool) const -{ - std::vector names; - source.listClasses(names); - std::vector summaries; - summaries.reserve(names.size()); - for (const std::string_view name : names) { - auto bytes = source.bytes(name); - if (bytes.is_err()) { - return ca::core::Err(std::move(bytes).unwrap_err()); - } - auto scanned = scan(bytes.unwrap(), pool); - if (scanned.is_err()) { - ConsumerError failure = std::move(scanned).unwrap_err(); - failure.message += " (class " + std::string{name} + ")"; - return ca::core::Err(std::move(failure)); - } - summaries.push_back(std::move(scanned).unwrap()); - } - return ca::core::Ok(std::move(summaries)); -} - -} // namespace mjbase::consumer diff --git a/mjbase/src/mjbase/consumer/summary_scanner.hpp b/mjbase/src/mjbase/consumer/summary_scanner.hpp deleted file mode 100644 index a412feb..0000000 --- a/mjbase/src/mjbase/consumer/summary_scanner.hpp +++ /dev/null @@ -1,81 +0,0 @@ -/// @file summary_scanner.hpp -/// @brief E3 骨架扫描:SummaryScanner 经 libjbc 读侧产出 ClassSummary。 -/// -/// 消费者对接协议(E 系)的类摘要面:对一份 class 字节做骨架扫描(类名/父/ -/// 接口/成员签名/hasCode/access/version),产出可跨类携带的值对象。语义与 -/// mbase `WorkspaceBase::listClassSummaries`(libjbc 薄切片口径)对齐: -/// internalName/superName/access/version/interfaceCount/fieldCount/methodCount -/// 逐字段可对拍(version 编码同口径:`(minor << 16) | major`);在计数之外 -/// 再交付成员签名(名字/描述符/access)与方法 hasCode,供消费方在不开会话 -/// 的前提下决定 promote 分流。 -/// -/// 载体纪律:ClassSummary 是纯值;名字/描述符/接口名以 mjbase::ClassKey 与 -/// KeyPool 驻留 id 表达(名字驻留调用方传入的作业池,池存活期内键稳定)。 -/// 失败 fail-closed:结构损坏(libjbc 读侧 Err)显式报错,不产出半截摘要; -/// 降级读入(Recovered,design.md §29 结构可确定族)照常出骨架。 -#pragma once - -#include - -#include "jbc/core/bytes.hpp" - -#include "mjbase/consumer/class_source.hpp" -#include "mjbase/keys.hpp" - -namespace mjbase::consumer { - -/// @brief 方法摘要:名字/描述符为 KeyPool 驻留 id + hasCode 分流位。 -struct SummaryMethod -{ - ca::u32 name = 0; ///< 名字(KeyPool id) - ca::u32 desc = 0; ///< 描述符(KeyPool id) - ca::u32 access = 0; ///< JVM access flags 原样 - bool hasCode = false; ///< 是否带 Code 属性(abstract/native 为 false) -}; - -/// @brief 字段摘要:名字/描述符为 KeyPool 驻留 id。 -struct SummaryField -{ - ca::u32 name = 0; ///< 名字(KeyPool id) - ca::u32 desc = 0; ///< 描述符(KeyPool id) - ca::u32 access = 0; ///< JVM access flags 原样 -}; - -/// @brief 类摘要值对象:一个类对协议消费方可见的全部骨架事实。 -/// -/// 成员序 = classfile 声明序。self/super/interfaces 驻留调用方 KeyPool; -/// super 为 invalid 键表示无父类条目(java/lang/Object 自身或 module-info, -/// 与 hierarchy::fillSummary 的空串映射同口径)。interfaceCount/ -/// fieldCount/methodCount 不另设字段:即三个容器的 size(与 mbase 口径的 -/// 计数逐字段相等)。 -struct ClassSummary -{ - ClassKey self; ///< 本类(内部名驻留键) - ClassKey super; ///< 父类;无父类条目时 invalid - std::vector interfaces; ///< 直接接口(superinterfaces) - std::vector methods; - std::vector fields; - ca::u32 access = 0; ///< 类 access flags 原样 - ca::u32 version = 0; ///< `(minor << 16) | major`(mbase 口径) -}; - -/// @brief E3 骨架扫描器(无状态;池由调用方持有)。 -class SummaryScanner -{ -public: - /// @brief 扫描单类字节骨架。 - /// - /// 经 libjbc readClass 纯扫描(preserveSourceBytes=false,不解属性体; - /// hasCode 按成员属性表有无 "Code" 槽判定,不解码 Code 内容)。读侧 - /// Err(结构损坏)转 Err(BadClassFile);Recovered 照常出骨架。 - ConsumerResult scan(jbc::core::ByteView bytes, KeyPool& pool) const; - - /// @brief 扫描一个类源全量(按 listClasses 序)。 - /// - /// fail-closed:任一类扫描失败即整批 Err(消息带类名),不静默跳过、 - /// 不返回部分结果——调用方要容忍缺失请逐类自行 scan。 - ConsumerResult> scanSource(const IClassSource& source, - KeyPool& pool) const; -}; - -} // namespace mjbase::consumer diff --git a/mjbase/test/unittest/consumer/class_source_test.cpp b/mjbase/test/unittest/consumer/class_source_test.cpp deleted file mode 100644 index 75267e1..0000000 --- a/mjbase/test/unittest/consumer/class_source_test.cpp +++ /dev/null @@ -1,239 +0,0 @@ -/// @file class_source_test.cpp -/// @brief E3 类源与骨架扫描单测:IClassSource 双实现 + SummaryScanner。 -/// -/// 测试类字节经 reference 测试共享的手拼汇编器构造(每字节受控,不依赖 -/// 本地语料);对拍口径与 golden 对拍测试(summary_parity_test)互补: -/// 本文件钉 API 行为(fail-closed/枚举序/first-wins/hasCode 分流),对拍 -/// 钉字段级一致。 -#include - -#include -#include -#include - -#include "jbc/core/opcode.hpp" - -#include "mjbase/consumer/class_source.hpp" -#include "mjbase/consumer/summary_scanner.hpp" -#include "shared/class_bytes_builder.hpp" - -namespace { - -using Bytes = std::vector; - -using mjbase::consumer::ClassSummary; -using mjbase::consumer::ContainerClassSource; -using mjbase::consumer::ErrorCode; -using mjbase::consumer::MemoryClassSource; -using mjbase::consumer::SummaryScanner; -using mjbase::reference::test_support::ClassBytesBuilder; -using mjbase::reference::test_support::FieldSpec; -using mjbase::reference::test_support::MethodSpec; - -/// 组一个最小类:2 方法(一个带体、一个 abstract)+ 1 字段 + 1 接口。 -Bytes makeSampleClass(std::string_view name) -{ - ClassBytesBuilder b(name); - b.setInterfaces({"com/i/Shape"}); - b.addField(FieldSpec{0x0002, "value", "I"}); - std::vector methods; - methods.push_back(MethodSpec{0x0009, "run", "()V", "", 1, 1, - [](mjbase::reference::test_support::CodeEmit& e) { - e.op(jbc::core::Op::RETURN); - }}); - methods.push_back(MethodSpec{0x0411, "shape", "()I"}); // public abstract:无 Code - return b.assemble(methods); -} - -/// E3-1:MemoryClassSource upsert / fail-closed NotFound / 字典序枚举。 -TEST(MjConsumerClassSourceTest, MemorySourceUpsertNotFoundAndOrder) -{ - MemoryClassSource source; - source.add("com/b/Beta", makeSampleClass("com/b/Beta")); - source.add("com/a/Alpha", makeSampleClass("com/a/Alpha")); - - ASSERT_EQ(source.classCount(), 2u); - - std::vector names; - source.listClasses(names); - ASSERT_EQ(names.size(), 2u); - EXPECT_EQ(names[0], "com/a/Alpha"); - EXPECT_EQ(names[1], "com/b/Beta"); - - auto bytes = source.bytes("com/b/Beta"); - ASSERT_TRUE(bytes.is_ok()); - EXPECT_GE(bytes.unwrap().size(), 8u); // CAFEBABE + 版本号 - - auto missing = source.bytes("com/c/Gamma"); - ASSERT_TRUE(missing.is_err()); - EXPECT_EQ(missing.unwrap_err().code, ErrorCode::NotFound); - EXPECT_NE(missing.unwrap_err().message.find("com/c/Gamma"), std::string::npos); - - // upsert:同名覆盖不新增条目。 - source.add("com/a/Alpha", makeSampleClass("com/a/Alpha")); - EXPECT_EQ(source.classCount(), 2u); -} - -/// E3-2:ContainerClassSource 构建——非 Class 资源忽略、URI≠内部名、 -/// 重复内部名 first-wins 并记录、字典序枚举。 -TEST(MjConsumerClassSourceTest, ContainerSourceProjectsClassResources) -{ - mjbase::model::ResourceContainer container; - container.addResource("lib/Beta.class", std::make_unique( - "lib/Beta.class", makeSampleClass("com/b/Beta"))); - container.addResource( - "META-INF/MANIFEST.MF", - std::make_unique(mjbase::jar::Manifest::fromBytes({}))); - container.addResource("assets/logo.png", - std::make_unique( - "assets/logo.png", Bytes{1, 2})); - container.addResource("lib/Alpha.class", std::make_unique( - "lib/Alpha.class", makeSampleClass("com/a/Alpha"))); - // 内部名重复(URI 不同):first-wins 保留首条。 - container.addResource("dup/Beta2.class", std::make_unique( - "dup/Beta2.class", makeSampleClass("com/b/Beta"))); - - auto made = ContainerClassSource::make(container); - ASSERT_TRUE(made.is_ok()); - - ContainerClassSource source = std::move(made).unwrap(); - EXPECT_EQ(source.classCount(), 2u); // manifest/unknown/重复条目都不计 - - std::vector names; - source.listClasses(names); - ASSERT_EQ(names.size(), 2u); - EXPECT_EQ(names[0], "com/a/Alpha"); - EXPECT_EQ(names[1], "com/b/Beta"); - - ASSERT_EQ(source.duplicateNames().size(), 1u); - EXPECT_EQ(source.duplicateNames()[0], "com/b/Beta"); - - // 字节来自首个 URI 的资源(内部名→资源映射正确)。 - const Bytes firstBytes = makeSampleClass("com/b/Beta"); - auto bytes = source.bytes("com/b/Beta"); - ASSERT_TRUE(bytes.is_ok()); - const jbc::core::ByteView view = bytes.unwrap(); - ASSERT_EQ(view.size(), firstBytes.size()); - EXPECT_EQ(0, std::memcmp(view.data(), firstBytes.data(), firstBytes.size())); - - auto missing = source.bytes("no/such/Name"); - ASSERT_TRUE(missing.is_err()); - EXPECT_EQ(missing.unwrap_err().code, ErrorCode::NotFound); -} - -/// E3-3:ContainerClassSource fail-closed——畸形 class 资源使 make 失败。 -TEST(MjConsumerClassSourceTest, ContainerSourceRejectsMalformedResource) -{ - mjbase::model::ResourceContainer container; - container.addResource("good/A.class", std::make_unique( - "good/A.class", makeSampleClass("good/A"))); - container.addResource("bad/B.class", std::make_unique( - "bad/B.class", Bytes{0xCA, 0xFE, 0xBA, 0xBE, 0x00})); - - auto made = ContainerClassSource::make(container); - ASSERT_TRUE(made.is_err()); - EXPECT_EQ(made.unwrap_err().code, ErrorCode::BadClassFile); - EXPECT_NE(made.unwrap_err().message.find("bad/B.class"), std::string::npos); -} - -/// E3-4:SummaryScanner——骨架字段、hasCode 分流、无父类映射、成员序 = 声明序。 -TEST(MjConsumerSummaryScannerTest, ScanSkeletonFieldsAndHasCode) -{ - const Bytes bytes = makeSampleClass("com/x/Sample"); - mjbase::KeyPool pool; - const SummaryScanner scanner; - auto scanned = scanner.scan(jbc::core::ByteView{bytes}, pool); - ASSERT_TRUE(scanned.is_ok()); - - const ClassSummary& s = scanned.unwrap(); - EXPECT_EQ(pool.str(s.self.id), "com/x/Sample"); - EXPECT_EQ(pool.str(s.super.id), "java/lang/Object"); - ASSERT_EQ(s.interfaces.size(), 1u); - EXPECT_EQ(pool.str(s.interfaces[0].id), "com/i/Shape"); - - ASSERT_EQ(s.methods.size(), 2u); - EXPECT_EQ(pool.str(s.methods[0].name), "run"); - EXPECT_EQ(pool.str(s.methods[0].desc), "()V"); - EXPECT_TRUE(s.methods[0].hasCode); - EXPECT_EQ(pool.str(s.methods[1].name), "shape"); - EXPECT_FALSE(s.methods[1].hasCode); // abstract:无 Code 属性 - - ASSERT_EQ(s.fields.size(), 1u); - EXPECT_EQ(pool.str(s.fields[0].name), "value"); - EXPECT_EQ(pool.str(s.fields[0].desc), "I"); -} - -/// E3-5:Object 自身无 super 条目 → invalid 键;version 编码 (minor<<16)|major。 -TEST(MjConsumerSummaryScannerTest, ObjectClassHasNoSuperAndVersionEncoding) -{ - ClassBytesBuilder builder("java/lang/Object"); - builder.setNoSuper(); // 无 super 条目(super_index = 0) - std::vector methods; - methods.push_back(MethodSpec{0x0009, "registerNatives", "()V"}); // static native:无体 - const Bytes bytes = builder.assemble(methods); - - mjbase::KeyPool pool; - const SummaryScanner scanner; - auto scanned = scanner.scan(jbc::core::ByteView{bytes}, pool); - ASSERT_TRUE(scanned.is_ok()); - - const ClassSummary& s = scanned.unwrap(); - EXPECT_FALSE(s.super.valid()); - EXPECT_TRUE(s.interfaces.empty()); - ASSERT_EQ(s.methods.size(), 1u); - EXPECT_FALSE(s.methods[0].hasCode); - // 手拼汇编器固定 V1_8(major=52, minor=0);编码与 mbase 口径一致。 - EXPECT_EQ(s.version, 52u); - EXPECT_EQ(s.version, (0u << 16) | 52u); -} - -/// E3-6:畸形字节 fail-closed——Err(BadClassFile),不产出半截摘要。 -TEST(MjConsumerSummaryScannerTest, MalformedBytesFailClosed) -{ - mjbase::KeyPool pool; - const SummaryScanner scanner; - const Bytes garbage{0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0x01}; - auto scanned = scanner.scan(jbc::core::ByteView{garbage}, pool); - ASSERT_TRUE(scanned.is_err()); - EXPECT_EQ(scanned.unwrap_err().code, ErrorCode::BadClassFile); -} - -/// E3-7:scanSource——多类批量成功;单类损坏整批 Err(fail-closed,不返回部分)。 -TEST(MjConsumerSummaryScannerTest, ScanSourceFailClosedOnFirstBadClass) -{ - MemoryClassSource source; - source.add("com/a/Alpha", makeSampleClass("com/a/Alpha")); - source.add("com/b/Beta", makeSampleClass("com/b/Beta")); - - mjbase::KeyPool pool; - const SummaryScanner scanner; - auto all = scanner.scanSource(source, pool); - ASSERT_TRUE(all.is_ok()); - ASSERT_EQ(all.unwrap().size(), 2u); - - source.add("com/c/Cursor", Bytes{0xCA, 0xFE, 0xBA, 0xBE, 0x00}); - auto failed = scanner.scanSource(source, pool); - ASSERT_TRUE(failed.is_err()); - EXPECT_EQ(failed.unwrap_err().code, ErrorCode::BadClassFile); - EXPECT_NE(failed.unwrap_err().message.find("com/c/Cursor"), std::string::npos); -} - -/// E3-8:scanSource 消费 ContainerClassSource——容器投影与扫描面打通。 -TEST(MjConsumerSummaryScannerTest, ScanSourceOverContainerSource) -{ - mjbase::model::ResourceContainer container; - container.addResource("lib/A.class", std::make_unique( - "lib/A.class", makeSampleClass("com/a/Alpha"))); - - auto made = ContainerClassSource::make(container); - ASSERT_TRUE(made.is_ok()); - - mjbase::KeyPool pool; - const SummaryScanner scanner; - auto summaries = scanner.scanSource(std::move(made).unwrap(), pool); - ASSERT_TRUE(summaries.is_ok()); - ASSERT_EQ(summaries.unwrap().size(), 1u); - EXPECT_EQ(pool.str(summaries.unwrap()[0].self.id), "com/a/Alpha"); -} - -} // namespace diff --git a/mjbase/test/unittest/consumer/summary_parity_test.cpp b/mjbase/test/unittest/consumer/summary_parity_test.cpp deleted file mode 100644 index 059e0f4..0000000 --- a/mjbase/test/unittest/consumer/summary_parity_test.cpp +++ /dev/null @@ -1,246 +0,0 @@ -/// @file summary_parity_test.cpp -/// @brief E3 骨架扫描对拍与失败面用例:mbase listClassSummaries 口径 -/// 逐字段对拍 + KeyPool 驻留稳定性 + fail-closed 错误路径。 -/// -/// 与 class_source_test.cpp 互补:那边钉 API 行为(fail-closed/枚举序/ -/// first-wins/hasCode 分流),本文件钉扫描产物的字段级口径—— -/// internalName/superName/access/version/interfaceCount/fieldCount/methodCount -/// 与 mbase WorkspaceBase::listClassSummaries 逐字段相等(version 编码同口径 -/// `(minor << 16) | major`);期望值全部 -/// 由夹具规格独立给出,不经被测代码换算。降级读入(Recovered)照常出骨架、 -/// 截断逐字节全 sweep 拒收,也在此钉死。 -#include - -#include -#include - -#include "jbc/core/bytes.hpp" - -#include "mjbase/consumer/class_source.hpp" -#include "mjbase/consumer/summary_scanner.hpp" -#include "mjbase/keys.hpp" -#include "shared/class_bytes_builder.hpp" - -namespace { - -using Bytes = std::vector; - -using mjbase::ClassKey; -using mjbase::KeyPool; -using mjbase::consumer::ClassSummary; -using mjbase::consumer::ErrorCode; -using mjbase::consumer::SummaryScanner; -using mjbase::reference::test_support::ClassBytesBuilder; -using mjbase::reference::test_support::FieldSpec; -using mjbase::reference::test_support::MethodSpec; - -/// 对拍夹具:public final 类、自定义父类、双接口、双字段、三方法 -///(一具体一 abstract 一 static final)——mbase 摘要的每个计数字段都非平凡。 -/// 汇编器固定 V1_8(minor=0, major=52)。 -Bytes makeParityClass() -{ - ClassBytesBuilder b("com/x/Sample"); - b.setAccess(0x0031); // public final super - b.setSuper("com/i/Base"); - b.setInterfaces({"com/i/ShapeA", "com/i/ShapeB"}); - b.addField(FieldSpec{0x0002, "count", "I"}); - b.addField(FieldSpec{0x001A, "name", "Ljava/lang/String;"}); // private static final - std::vector methods; - methods.push_back(MethodSpec{0x0009, "run", "()V", "", 1, 1, - [](mjbase::reference::test_support::CodeEmit& e) { - e.op(jbc::core::Op::RETURN); - }}); - methods.push_back(MethodSpec{0x0401, "shape", "(I)I"}); // public abstract:无体 - methods.push_back(MethodSpec{0x001A, "describe", "(Ljava/lang/Object;)Ljava/lang/String;", "", 1, 1, - [](mjbase::reference::test_support::CodeEmit& e) { - e.op(jbc::core::Op::ACONST_NULL); - e.op(jbc::core::Op::ARETURN); - }}); - return b.assemble(methods); -} - -/// E3-9:mbase listClassSummaries 口径逐字段对拍——期望值由夹具规格独立给出, -/// 成员序 = classfile 声明序(计数即容器 size,与 mbase 计数口径相等)。 -TEST(MjConsumerSummaryParityTest, FieldsMatchMbaseListClassSummariesContract) -{ - const Bytes bytes = makeParityClass(); - KeyPool pool; - const SummaryScanner scanner; - auto scanned = scanner.scan(jbc::core::ByteView{bytes}, pool); - ASSERT_TRUE(scanned.is_ok()); - const ClassSummary& s = scanned.unwrap(); - - // mbase 摘要七字段逐一对拍(internalName/superName/access/version/ - // interfaceCount/fieldCount/methodCount)。 - EXPECT_EQ(pool.str(s.self.id), "com/x/Sample"); - EXPECT_EQ(pool.str(s.super.id), "com/i/Base"); - EXPECT_EQ(s.access, 0x0031u); - EXPECT_EQ(s.version, 52u); // V1_8 = (0 << 16) | 52 - ASSERT_EQ(s.interfaces.size(), 2u); // interfaceCount - ASSERT_EQ(s.fields.size(), 2u); // fieldCount - ASSERT_EQ(s.methods.size(), 3u); // methodCount - - // 接口序 = classfile 声明序。 - EXPECT_EQ(pool.str(s.interfaces[0].id), "com/i/ShapeA"); - EXPECT_EQ(pool.str(s.interfaces[1].id), "com/i/ShapeB"); - - // 成员签名(消费方在计数之外的交付面):名字/描述符/access 按声明序。 - EXPECT_EQ(pool.str(s.methods[0].name), "run"); - EXPECT_EQ(pool.str(s.methods[0].desc), "()V"); - EXPECT_EQ(s.methods[0].access, 0x0009u); - EXPECT_TRUE(s.methods[0].hasCode); - EXPECT_EQ(pool.str(s.methods[1].name), "shape"); - EXPECT_EQ(pool.str(s.methods[1].desc), "(I)I"); - EXPECT_EQ(s.methods[1].access, 0x0401u); - EXPECT_FALSE(s.methods[1].hasCode); - EXPECT_EQ(pool.str(s.methods[2].name), "describe"); - EXPECT_EQ(pool.str(s.methods[2].desc), "(Ljava/lang/Object;)Ljava/lang/String;"); - EXPECT_EQ(s.methods[2].access, 0x001Au); - - EXPECT_EQ(pool.str(s.fields[0].name), "count"); - EXPECT_EQ(pool.str(s.fields[0].desc), "I"); - EXPECT_EQ(s.fields[0].access, 0x0002u); - EXPECT_EQ(pool.str(s.fields[1].name), "name"); - EXPECT_EQ(pool.str(s.fields[1].desc), "Ljava/lang/String;"); - EXPECT_EQ(s.fields[1].access, 0x001Au); -} - -/// E3-10:version 编码 `(minor << 16) | major`——minor 非零时高位段生效 -///(汇编器固定 minor=0,这里按 classfile 布局直接改头 4..5 字节)。 -TEST(MjConsumerSummaryParityTest, VersionEncodesMinorInHighBits) -{ - Bytes bytes = makeParityClass(); - ASSERT_GE(bytes.size(), 8u); - bytes[4] = 0x00; - bytes[5] = 0x03; // minor_version = 3(big-endian,偏移 4..5) - - KeyPool pool; - const SummaryScanner scanner; - auto scanned = scanner.scan(jbc::core::ByteView{bytes}, pool); - ASSERT_TRUE(scanned.is_ok()); - EXPECT_EQ(scanned.unwrap().version, (3u << 16) | 52u); -} - -/// E3-11:KeyPool 驻留稳定性——同字节重扫恒等同 id;预驻留名字复用既有 id; -/// 池继续增长不改变既有键;invalid super(Object)不新占驻留槽。 -TEST(MjConsumerSummaryParityTest, KeyPoolInterningIsStableAcrossScans) -{ - const Bytes bytes = makeParityClass(); - KeyPool pool; - const SummaryScanner scanner; - - auto first = scanner.scan(jbc::core::ByteView{bytes}, pool); - ASSERT_TRUE(first.is_ok()); - const ClassSummary one = first.unwrap(); - - // 预驻留一个将出现在摘要里的名字:扫描必须复用同一 id。 - const ca::u32 preInterned = pool.intern("com/i/Base"); - - auto second = scanner.scan(jbc::core::ByteView{bytes}, pool); - ASSERT_TRUE(second.is_ok()); - const ClassSummary two = second.unwrap(); - - EXPECT_EQ(two.self.id, one.self.id); - EXPECT_EQ(two.super.id, one.super.id); - EXPECT_EQ(two.super.id, preInterned); - ASSERT_EQ(two.interfaces.size(), one.interfaces.size()); - for (ca::usize i = 0; i < two.interfaces.size(); ++i) { - EXPECT_EQ(two.interfaces[i].id, one.interfaces[i].id); - } - ASSERT_EQ(two.methods.size(), one.methods.size()); - for (ca::usize i = 0; i < two.methods.size(); ++i) { - EXPECT_EQ(two.methods[i].name, one.methods[i].name); - EXPECT_EQ(two.methods[i].desc, one.methods[i].desc); - EXPECT_EQ(two.methods[i].access, one.methods[i].access); - EXPECT_EQ(two.methods[i].hasCode, one.methods[i].hasCode); - } - ASSERT_EQ(two.fields.size(), one.fields.size()); - for (ca::usize i = 0; i < two.fields.size(); ++i) { - EXPECT_EQ(two.fields[i].name, one.fields[i].name); - EXPECT_EQ(two.fields[i].desc, one.fields[i].desc); - } - - // 不同内容不同 id;池再驻留新类名后既有键不变(视图稳定)。 - EXPECT_NE(one.self.id, one.super.id); - const ca::u32 selfBefore = one.self.id; - const ca::u32 other = pool.intern("com/y/Other"); - EXPECT_EQ(pool.str(selfBefore), "com/x/Sample"); - EXPECT_NE(other, selfBefore); -} - -/// E3-12:Object 自身无 super 条目——invalid 键且不新占驻留槽(0 号槽即 -/// 空内容哨兵,扫描不得为空父名追加条目)。 -TEST(MjConsumerSummaryParityTest, MissingSuperConsumesNoPoolSlot) -{ - ClassBytesBuilder builder("java/lang/Object"); - builder.setNoSuper(); - std::vector methods; - methods.push_back(MethodSpec{0x0009, "registerNatives", "()V"}); // static native:无体 - const Bytes bytes = builder.assemble(methods); - - KeyPool pool; - const SummaryScanner scanner; - auto scanned = scanner.scan(jbc::core::ByteView{bytes}, pool); - ASSERT_TRUE(scanned.is_ok()); - const ClassSummary& s = scanned.unwrap(); - EXPECT_FALSE(s.super.valid()); - EXPECT_TRUE(pool.str(s.super.id).empty()); - // 槽位账:0 号哨兵 + this + 方法名 + 描述符,恰 4 条,无空父名条目。 - EXPECT_EQ(pool.size(), 4u); -} - -/// E3-13:截断 fail-closed——合法类字节从头到尾逐字节截断(全部前缀长度), -/// 每个截断点都必须 Err(BadClassFile),绝不产出半截摘要。 -TEST(MjConsumerSummaryParityTest, EveryTruncationLengthIsRejected) -{ - const Bytes bytes = makeParityClass(); - KeyPool pool; - const SummaryScanner scanner; - ca::usize rejected = 0; - for (ca::usize cut = 0; cut < bytes.size(); ++cut) { - auto scanned = scanner.scan(jbc::core::ByteView{bytes.data(), cut}, pool); - ASSERT_TRUE(scanned.is_err()) << "truncation accepted at length " << cut; - EXPECT_EQ(scanned.unwrap_err().code, ErrorCode::BadClassFile); - ++rejected; - } - EXPECT_EQ(rejected, bytes.size()); -} - -/// E3-14:降级读入(Recovered,结构可确定族)照常出完整骨架——尾部多余 -/// 字节不拒收、不缺字段,骨架与干净字节扫描逐字段等同。 -TEST(MjConsumerSummaryParityTest, RecoveredTrailingBytesStillYieldFullSkeleton) -{ - const Bytes clean = makeParityClass(); - - KeyPool pool; - const SummaryScanner scanner; - auto baseline = scanner.scan(jbc::core::ByteView{clean}, pool); - ASSERT_TRUE(baseline.is_ok()); - const ClassSummary cleanSummary = baseline.unwrap(); - - Bytes degraded = clean; - degraded.push_back(0xDE); - degraded.push_back(0xAD); - degraded.push_back(0xBE); - degraded.push_back(0xEF); - auto scanned = scanner.scan(jbc::core::ByteView{degraded}, pool); - ASSERT_TRUE(scanned.is_ok()); - const ClassSummary& s = scanned.unwrap(); - - EXPECT_EQ(s.self.id, cleanSummary.self.id); - EXPECT_EQ(s.super.id, cleanSummary.super.id); - ASSERT_EQ(s.interfaces.size(), cleanSummary.interfaces.size()); - ASSERT_EQ(s.methods.size(), cleanSummary.methods.size()); - ASSERT_EQ(s.fields.size(), cleanSummary.fields.size()); - EXPECT_EQ(s.access, cleanSummary.access); - EXPECT_EQ(s.version, cleanSummary.version); - for (ca::usize i = 0; i < s.methods.size(); ++i) { - EXPECT_EQ(s.methods[i].name, cleanSummary.methods[i].name); - EXPECT_EQ(s.methods[i].hasCode, cleanSummary.methods[i].hasCode); - } - for (ca::usize i = 0; i < s.fields.size(); ++i) { - EXPECT_EQ(s.fields[i].name, cleanSummary.fields[i].name); - } -} - -} // namespace diff --git a/mjbase/xmake.lua b/mjbase/xmake.lua index 5b84ea6..3a250cb 100644 --- a/mjbase/xmake.lua +++ b/mjbase/xmake.lua @@ -90,7 +90,6 @@ target("mjbase") add_files("src/mjbase/mapping/**.cpp") add_files("src/mjbase/model/**.cpp") add_files("src/mjbase/project/**.cpp") - add_files("src/mjbase/consumer/**.cpp") -- 批 2:asm 指令工程域(零内依赖的六个子目录)+ hierarchy(只依赖 keys)。 -- asm/classpath 只收 call_graph:resolved_call_graph 语义上属 reference -- (module-map 第五批),留在批 3 与 analysis/reference 一同落仓, @@ -161,7 +160,6 @@ target("mjbase_unittest") add_files("test/unittest/mapping/*_test.cpp") add_files("test/unittest/model/*_test.cpp") add_files("test/unittest/project/*_test.cpp") - add_files("test/unittest/consumer/*_test.cpp") -- 批 2:与上面 src 的 asm 子目录、hierarchy 一一对应。 add_files("test/unittest/asm/analyzer/*_test.cpp") add_files("test/unittest/asm/cfg/*_test.cpp")