feat(tools): 边界审计支持逐字段位游标追踪

This commit is contained in:
TermiNexus committed 2026-10-21 02:48:17 +08:00
1 parent ffe633b286
commit fafdb91a04
2 files changed
+32

No files matched your search

+18
View File
@@ -45,11 +45,22 @@ const Alias = {};
for (const [N, R] of Object.entries(Tables.ResourceClasses || {})) Alias[N] = R.Wire;
for (const [N, W] of Object.entries(RESOURCE_CLASS_WIRE)) Alias[N] = W;
// --trace <文件名>:<项序号>:只对这一项打开逐字段追踪,打印「字段 / 字节区间 / 位游标前后」
const TraceArg = Arg('trace', '');
const TraceDepth = Number(Arg('trace-depth', '6')) || 6;
let TraceOn = false;
const Reader = MakeReader(Tables, {
EnumBytes: Number(Arg('enum-bytes', '4')) || 4,
OptBits: true,
TypeAlias: Alias,
StringLiteralOnly: true,
OnField: (I) => {
if (!TraceOn || I.Depth > TraceDepth) return;
console.log(' ' + ' '.repeat(I.Depth) + I.Name + ' <' + I.Type + '> ' +
I.Start + '→' + I.Next + ' bit ' + I.BitBefore + '→' + I.BitAfter +
(I.Bad ? ' BAD ' + I.Why : ''));
},
});
const PT_METHOD = '7640916300855664666';
@@ -152,7 +163,14 @@ function AuditPt(Body, Off, File) {
return;
}
const BitBefore = Bits ? Reader.BitCursor().Index : 0;
const TraceNow = TraceArg && path.basename(File) === TraceArg.split(':')[0] && String(I + 1) === TraceArg.split(':')[1];
if (TraceNow) {
console.log(' [trace] ' + path.basename(File) + ' 第 ' + (I + 1) + ' 项 ' + Id + '(' + Key + ')负载@' + At +
' 起始 bit=' + BitBefore);
TraceOn = true;
}
const R = Reader.ReadFields(Buf, At, Codec, 0);
TraceOn = false;
Stats.Items++;
// 「解不动」的判据要包含位游标:整条模型字段全是 opt 且都为空时,字节不推进但位在推进,
// 这也是合法的(例如 DescriptionMediaCC 只有两个 opt<i64>)。
+14
View File
@@ -109,6 +109,9 @@ function SplitTypeArgs(Text) {
function MakeReader(Tables, Options) {
const Opts = Options || {};
const EnumBytes = Opts.EnumBytes === undefined ? 4 : Opts.EnumBytes;
// OnField:逐字段回调(诊断用)。参数 {Depth, Name, Type, Start, Next, BitBefore, BitAfter, Bad, Why}
// —— 只读观测,不参与解码判定;审计器用它打印「每个字段消费了哪些字节/位」。
const OnField = Opts.OnField || null;
// OptBits:opt 的「空/非空」改从 nullmap 比特流读 1 个 bit(见文件头注释)。
// 默认 false = 老口径(1 字节标志),也就是「字段表」那条路径的既有行为,不动它。
const OptBits = !!Opts.OptBits;
@@ -315,10 +318,21 @@ function MakeReader(Tables, Options) {
let Bad = false;
let Why = '';
for (const F of (Codec && Codec.Fields) || []) {
const BitBefore = BitIndex;
const R = ReadValue(Buf, At, F.Type, F.TypeCodec, Depth);
// 匿名字段(表里没有名字,只有线宽)用序号区分,否则会同名互相覆盖
const Key = F.Name === '(匿名)' || F.Name === '?' ? '(匿名#' + F.Order + ')' : F.Name;
Obj[Key] = R.Value;
if (OnField) {
try {
OnField({
Depth: Depth || 0, Name: Key, Type: F.Type, Start: At, Next: R.Next,
BitBefore, BitAfter: BitIndex, Bad: !!R.Bad, Why: R.Why || '',
});
} catch (E) {
// 诊断回调不允许影响解码
}
}
if (R.Bad) {
Bad = true;
// 诊断用的原因串:哪个字段、什么类型、卡在负载内第几字节、内层如果直接说得出原因也带上