- Stb:kHeaderSize 104、kSha256Size 32;SHA-256 实现(FIPS 180-4,含标准测试向量验证); fill_model_id(name+version 拼 32 字节补 '\0');StbView 解析新格式 + model_id()/model_matches()/sha_ok()(数据段不含尾、缺尾报错) - Codegen:assemble_image 写型号标识(偏移 72)+ 文件尾 SHA-256 - main.cpp:--disasm 显示 model/sha;写后自检(ok+sha+型号匹配,失败报错) - 测试:machine_test +SHA-256 已知向量(空串/abc)与型号标识断言(53 断言); codegen_test data_len 断言 +32(SHA 尾);ctest 12/12 - 验证:line1 288 字节 model=STATOR1 sha=ok;篡改一字节 sha=BAD
330 lines
14 KiB
C++
330 lines
14 KiB
C++
/**
|
||
* @file machine_test.cpp
|
||
* @brief machine.toml 加载与强校验测试(阶段 A 步骤 3)
|
||
* @author
|
||
* @date 2026-08-21
|
||
*/
|
||
|
||
#include <cstdio>
|
||
#include <cstring>
|
||
#include <filesystem>
|
||
#include <string>
|
||
|
||
#include "compiler/MachineConfig.h"
|
||
#include "compiler/Stb.h"
|
||
#include "compiler/TypeInfo.h"
|
||
|
||
#ifndef REPO_ROOT
|
||
#define REPO_ROOT "."
|
||
#endif
|
||
|
||
static int g_checks = 0;
|
||
|
||
#define CHECK(cond) \
|
||
do { \
|
||
if (!(cond)) { \
|
||
std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
|
||
return false; \
|
||
} \
|
||
++g_checks; \
|
||
} while (0)
|
||
|
||
// ---- 写临时 toml ----
|
||
|
||
static std::string write_tmp(const char* name, const char* content) {
|
||
const std::filesystem::path dir = std::filesystem::temp_directory_path();
|
||
const std::filesystem::path path = dir / name;
|
||
std::FILE* f = std::fopen(path.string().c_str(), "w");
|
||
std::fputs(content, f);
|
||
std::fclose(f);
|
||
return path.string();
|
||
}
|
||
|
||
static bool expect_load_err(const char* name, const char* content, const char* keyword) {
|
||
const std::string path = write_tmp(name, content);
|
||
compiler::MachineConfig cfg;
|
||
std::string err;
|
||
if (cfg.load(path, &err)) {
|
||
std::printf("FAIL %s: loaded ok\n", name);
|
||
std::remove(path.c_str());
|
||
return false;
|
||
}
|
||
if (err.find("machine error") != 0) {
|
||
std::printf("FAIL %s: want 'machine error', got '%s'\n", name, err.c_str());
|
||
std::remove(path.c_str());
|
||
return false;
|
||
}
|
||
if (err.find(keyword) == std::string::npos) {
|
||
std::printf("FAIL %s: want '%s', got '%s'\n", name, keyword, err.c_str());
|
||
std::remove(path.c_str());
|
||
return false;
|
||
}
|
||
++g_checks;
|
||
std::remove(path.c_str());
|
||
return true;
|
||
}
|
||
|
||
// 合法内容模板(可注入损坏)
|
||
static const char* kGood =
|
||
"[meta]\nname = \"STATOR\"\nversion = 1\n"
|
||
"[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nrange = [0, 1]\ntag = 0\n"
|
||
"[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n"
|
||
"[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n"
|
||
"[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n"
|
||
"[[op]]\nname = \"CAL_TON\"\nopcode = 24\nclass = \"instance\"\nformat = \"CAL\"\nparams = [\"instance\"]\nenabled = true\n"
|
||
"[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n"
|
||
"[[fb]]\nname = \"ton\"\nopcode = 24\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n";
|
||
|
||
// ---- 1. 正例:仓库 machine.toml ----
|
||
|
||
static bool test_positive() {
|
||
compiler::MachineConfig cfg;
|
||
std::string err;
|
||
CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err));
|
||
CHECK(cfg.ok());
|
||
CHECK(cfg.model_name() == "STATOR");
|
||
CHECK(cfg.version() == 1);
|
||
|
||
CHECK(cfg.types().size() == 3);
|
||
const compiler::ConfigType* bt = cfg.find_type("BOOL");
|
||
CHECK(bt != nullptr && bt->base == "uint8" && bt->has_range && bt->range_min == 0 &&
|
||
bt->range_max == 1 && bt->tag == 0);
|
||
CHECK(cfg.find_type("INT") != nullptr && cfg.find_type("INT")->tag == 1);
|
||
CHECK(cfg.find_type("TIME") != nullptr && cfg.find_type("TIME")->tag == 2);
|
||
CHECK(cfg.find_type("REAL") == nullptr);
|
||
|
||
CHECK(cfg.ops().size() == 34);
|
||
const compiler::ConfigOp* add = cfg.find_op("ADD");
|
||
CHECK(add != nullptr && add->opcode == 5 && add->format == "RRR" && !add->is_instance &&
|
||
add->params.size() == 3);
|
||
const compiler::ConfigOp* ton = cfg.find_op("CAL_TON");
|
||
CHECK(ton != nullptr && ton->opcode == 24 && ton->is_instance && ton->format == "CAL");
|
||
CHECK(cfg.find_op("CAL_F_TRIG") != nullptr && cfg.find_op("CAL_F_TRIG")->opcode == 31);
|
||
CHECK(cfg.find_op_by_code(33) != nullptr && cfg.find_op_by_code(33)->name == "RET");
|
||
CHECK(cfg.op_enabled(0) && cfg.op_enabled(24));
|
||
CHECK(cfg.find_op("NOPE") == nullptr);
|
||
|
||
CHECK(cfg.fbs().size() == 8);
|
||
const compiler::ConfigFb* ctud = cfg.find_fb("ctud");
|
||
CHECK(ctud != nullptr && ctud->opcode == 29 && ctud->fields.size() == 8);
|
||
CHECK(cfg.find_fb("r_trig") != nullptr && cfg.find_fb("r_trig")->fields.size() == 2);
|
||
|
||
// 基元表
|
||
CHECK(compiler::MachineConfig::find_prim("int16") != nullptr);
|
||
const compiler::PrimType* p = compiler::MachineConfig::find_prim("uint8");
|
||
CHECK(p != nullptr && p->width == 1 && !p->is_signed && !p->is_float);
|
||
CHECK(compiler::MachineConfig::find_prim("float64") != nullptr);
|
||
CHECK(compiler::MachineConfig::find_prim("bfloat16") == nullptr);
|
||
return true;
|
||
}
|
||
|
||
// ---- 2. 负例:9 类 ----
|
||
|
||
static bool test_negative() {
|
||
// 缺属性(enabled 缺失)
|
||
if (!expect_load_err(
|
||
"mc_bad1.toml",
|
||
"[meta]\nname = \"S\"\nversion = 1\n"
|
||
"[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n"
|
||
"[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n"
|
||
"[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n"
|
||
"[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\", \"rs\"]\n",
|
||
"missing field 'enabled'")) {
|
||
return false;
|
||
}
|
||
// base 未命中基元
|
||
if (!expect_load_err(
|
||
"mc_bad2.toml",
|
||
"[meta]\nname = \"S\"\nversion = 1\n"
|
||
"[[type]]\nname = \"BOOL\"\nbase = \"bigint\"\ntag = 0\n"
|
||
"[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n"
|
||
"[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n"
|
||
"[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n",
|
||
"unknown base")) {
|
||
return false;
|
||
}
|
||
// range min > max
|
||
if (!expect_load_err(
|
||
"mc_bad3.toml",
|
||
"[meta]\nname = \"S\"\nversion = 1\n"
|
||
"[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nrange = [1, 0]\ntag = 0\n"
|
||
"[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n"
|
||
"[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n"
|
||
"[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n",
|
||
"range min > max")) {
|
||
return false;
|
||
}
|
||
// tag 契约破坏(BOOL tag=5)
|
||
if (!expect_load_err(
|
||
"mc_bad4.toml",
|
||
"[meta]\nname = \"S\"\nversion = 1\n"
|
||
"[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 5\n"
|
||
"[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n"
|
||
"[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n"
|
||
"[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n",
|
||
"tag out of contract")) {
|
||
return false;
|
||
}
|
||
// tag 契约破坏(INT 占 tag 0)
|
||
if (!expect_load_err(
|
||
"mc_bad5.toml",
|
||
"[meta]\nname = \"S\"\nversion = 1\n"
|
||
"[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 1\n"
|
||
"[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 0\n"
|
||
"[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n"
|
||
"[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n",
|
||
"type contract broken")) {
|
||
return false;
|
||
}
|
||
// opcode 重号
|
||
if (!expect_load_err(
|
||
"mc_bad6.toml",
|
||
"[meta]\nname = \"S\"\nversion = 1\n"
|
||
"[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n"
|
||
"[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n"
|
||
"[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n"
|
||
"[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n"
|
||
"[[op]]\nname = \"NOPE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"a\", \"b\"]\nenabled = true\n",
|
||
"duplicate opcode")) {
|
||
return false;
|
||
}
|
||
// 类别-格式互锁破坏(instance + RRR)
|
||
if (!expect_load_err(
|
||
"mc_bad7.toml",
|
||
"[meta]\nname = \"S\"\nversion = 1\n"
|
||
"[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n"
|
||
"[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n"
|
||
"[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n"
|
||
"[[op]]\nname = \"BAD\"\nopcode = 24\nclass = \"instance\"\nformat = \"RRR\"\nparams = [\"a\", \"b\", \"c\"]\nenabled = true\n",
|
||
"class-format mismatch")) {
|
||
return false;
|
||
}
|
||
// params 数量与 format 不符
|
||
if (!expect_load_err(
|
||
"mc_bad8.toml",
|
||
"[meta]\nname = \"S\"\nversion = 1\n"
|
||
"[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n"
|
||
"[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n"
|
||
"[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n"
|
||
"[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\"]\nenabled = true\n",
|
||
"params count")) {
|
||
return false;
|
||
}
|
||
// fb.opcode 不匹配 instance op
|
||
if (!expect_load_err(
|
||
"mc_bad9.toml",
|
||
"[meta]\nname = \"S\"\nversion = 1\n"
|
||
"[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n"
|
||
"[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n"
|
||
"[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n"
|
||
"[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n"
|
||
"[[fb]]\nname = \"ton\"\nopcode = 33\nfields = [[\"in\", \"BOOL\"]]\n",
|
||
"must match an instance op")) {
|
||
return false;
|
||
}
|
||
// fb 字段类型未命中 type 表
|
||
if (!expect_load_err(
|
||
"mc_bad10.toml",
|
||
"[meta]\nname = \"S\"\nversion = 1\n"
|
||
"[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n"
|
||
"[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n"
|
||
"[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n"
|
||
"[[op]]\nname = \"CAL_TON\"\nopcode = 24\nclass = \"instance\"\nformat = \"CAL\"\nparams = [\"instance\"]\nenabled = true\n"
|
||
"[[fb]]\nname = \"ton\"\nopcode = 24\nfields = [[\"in\", \"REAL\"]]\n",
|
||
"unknown field type")) {
|
||
return false;
|
||
}
|
||
// 语法错误
|
||
if (!expect_load_err("mc_bad11.toml", "[meta\nname = \"S\"\n", "parse error")) {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// ---- 3. 合法最小配置正例 ----
|
||
|
||
static bool test_positive_min() {
|
||
const std::string path = write_tmp("mc_good.toml", kGood);
|
||
compiler::MachineConfig cfg;
|
||
std::string err;
|
||
CHECK(cfg.load(path, &err));
|
||
CHECK(cfg.ops().size() == 3);
|
||
CHECK(cfg.fbs().size() == 1);
|
||
CHECK(cfg.find_fb("ton")->fields.size() == 4);
|
||
std::remove(path.c_str());
|
||
return true;
|
||
}
|
||
|
||
// ---- 4. 类型元数据(TypeInfo)----
|
||
|
||
static bool test_type_meta() {
|
||
compiler::MachineConfig cfg;
|
||
std::string err;
|
||
CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err));
|
||
|
||
// BOOL:base=uint8 → 1 字节、无符号、非浮点、tag 0、range [0,1]
|
||
const compiler::TypeMeta b = compiler::type_meta(cfg, "BOOL");
|
||
CHECK(b && b.width() == 1 && !b.is_signed() && !b.is_float() && b.tag() == 0);
|
||
CHECK(b.has_range() && b.value_in_range(0) && b.value_in_range(1));
|
||
CHECK(!b.value_in_range(2) && !b.value_in_range(-1));
|
||
// 大小写不敏感(Linker type_name 是小写)
|
||
const compiler::TypeMeta b2 = compiler::type_meta(cfg, "bool");
|
||
CHECK(b2 && b2.tag() == 0 && b2.width() == 1);
|
||
|
||
// INT:int16 → 2 字节、有符号、tag 1、无 range
|
||
const compiler::TypeMeta i = compiler::type_meta(cfg, "int");
|
||
CHECK(i && i.width() == 2 && i.is_signed() && !i.is_float() && i.tag() == 1);
|
||
CHECK(!i.has_range() || i.value_in_range(30000));
|
||
|
||
// TIME:int64 → 8 字节、有符号、tag 2
|
||
const compiler::TypeMeta t = compiler::type_meta(cfg, "TIME");
|
||
CHECK(t && t.width() == 8 && t.is_signed() && !t.is_float() && t.tag() == 2);
|
||
|
||
// 未定义类型 → 空
|
||
CHECK(!compiler::type_meta(cfg, "REAL"));
|
||
CHECK(!compiler::type_meta(cfg, ""));
|
||
return true;
|
||
}
|
||
|
||
// ---- 5. SHA-256 已知向量 + 型号标识 ----
|
||
|
||
static bool test_sha256() {
|
||
// 标准测试向量
|
||
{
|
||
uint8_t out[compiler::kSha256Size];
|
||
compiler::sha256(nullptr, 0, out);
|
||
const uint8_t want[32] = {0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14,
|
||
0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
|
||
0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c,
|
||
0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55};
|
||
CHECK(std::memcmp(out, want, 32) == 0);
|
||
}
|
||
{
|
||
const uint8_t abc[] = {'a', 'b', 'c'};
|
||
uint8_t out[compiler::kSha256Size];
|
||
compiler::sha256(abc, 3, out);
|
||
const uint8_t want[32] = {0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea,
|
||
0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
|
||
0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c,
|
||
0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad};
|
||
CHECK(std::memcmp(out, want, 32) == 0);
|
||
}
|
||
// 型号标识:STATOR + 1 → "STATOR1" 补 '\0'
|
||
char mid[compiler::kModelIdSize];
|
||
compiler::fill_model_id("STATOR", 1, mid);
|
||
CHECK(std::strncmp(mid, "STATOR1", 7) == 0);
|
||
CHECK(mid[7] == '\0');
|
||
CHECK(mid[31] == '\0');
|
||
return true;
|
||
}
|
||
|
||
int main() {
|
||
if (!test_positive()) return 1;
|
||
if (!test_negative()) return 1;
|
||
if (!test_positive_min()) return 1;
|
||
if (!test_type_meta()) return 1;
|
||
if (!test_sha256()) return 1;
|
||
std::printf("machine_test: %d checks passed\n", g_checks);
|
||
return 0;
|
||
}
|