hm-pt-core 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,9 +21,10 @@ export function decodeRawByRegistry(raw, registry, options = {}) {
21
21
  };
22
22
  }
23
23
  const allFields = parseAllFieldsFromRaw(raw, fmt);
24
+ const renderContext = { eventName: fmt.eventName, eventId };
24
25
  const mode = options.tracePrintMode ?? "printf";
25
26
  if (fmt.printFmt && mode === "fieldDict") {
26
- let renderedFieldDict = renderPrintFmtAsFieldDict(fmt.printFmt, fmt.printArgs, allFields);
27
+ let renderedFieldDict = renderPrintFmtAsFieldDict(fmt.printFmt, fmt.printArgs, allFields, renderContext);
27
28
  const transform = registry.transformFieldDictByEventId?.get(eventId);
28
29
  if (transform) {
29
30
  renderedFieldDict = transform({
@@ -42,7 +43,9 @@ export function decodeRawByRegistry(raw, registry, options = {}) {
42
43
  skipped: false,
43
44
  };
44
45
  }
45
- const renderedText = fmt.printFmt ? renderPrintFmt(fmt.printFmt, fmt.printArgs, allFields) : undefined;
46
+ const renderedText = fmt.printFmt
47
+ ? renderPrintFmt(fmt.printFmt, fmt.printArgs, allFields, renderContext)
48
+ : undefined;
46
49
  return {
47
50
  commonType: eventId,
48
51
  commonFields,
@@ -222,13 +222,89 @@ function isCharType(typeName) {
222
222
  const t = normalizeType(typeName);
223
223
  return t === "char" || t === "signed char" || t === "unsigned char";
224
224
  }
225
+ function isDataLocType(typeName) {
226
+ return normalizeType(typeName).includes("__data_loc");
227
+ }
228
+ function parseDataLocElementType(typeName, signed) {
229
+ const t = normalizeType(typeName);
230
+ const m = t.match(/^__data_loc\s+(.+)\[\]$/);
231
+ if (!m)
232
+ return undefined;
233
+ const elemTypeName = m[1].trim();
234
+ const elemSize = inferScalarSize(elemTypeName, signed);
235
+ if (elemSize === undefined)
236
+ return undefined;
237
+ return { elemTypeName, elemSize, signed: isSignedElementType(elemTypeName, signed) };
238
+ }
239
+ function isSignedElementType(elemTypeName, fieldSigned) {
240
+ const t = normalizeType(elemTypeName);
241
+ if (t.includes("unsigned"))
242
+ return false;
243
+ if (t === "char" && !fieldSigned)
244
+ return false;
245
+ return fieldSigned || t === "signed char" || t === "short" || t === "int" || t === "long" || t === "long long" || t === "__s64";
246
+ }
247
+ function inferScalarSize(typeName, signed) {
248
+ const t = normalizeType(typeName);
249
+ if (t.includes("double"))
250
+ return 8;
251
+ if (t.includes("long long") || t === "__s64")
252
+ return 8;
253
+ if (t.includes("long"))
254
+ return 8;
255
+ if (t.includes("short"))
256
+ return 2;
257
+ if (t.includes("char") || t.includes("int") || t.includes("float"))
258
+ return 4;
259
+ if (t.includes("unsigned") || signed)
260
+ return 4;
261
+ return undefined;
262
+ }
263
+ function assertFieldInBounds(fieldName, offset, size, rawLength) {
264
+ if (offset < 0 || offset + size > rawLength) {
265
+ throw new TraceFieldDecodeError(`Field "${fieldName}" out of bounds: offset=${offset}, size=${size}, raw.length=${rawLength}`, fieldName, offset, size, rawLength);
266
+ }
267
+ }
268
+ function readLocatorField(view, field) {
269
+ assertFieldInBounds(field.name, field.offset, field.size, view.byteLength);
270
+ const locField = {
271
+ ...field,
272
+ typeName: "unsigned int",
273
+ signed: false,
274
+ size: 4,
275
+ };
276
+ const loc = readFieldScalarUnchecked(view, locField);
277
+ if (typeof loc !== "number") {
278
+ throw new TraceFieldDecodeError(`Field "${field.name}" __data_loc locator is invalid`, field.name, field.offset, field.size, view.byteLength);
279
+ }
280
+ return loc;
281
+ }
282
+ function readDynamicRegionAsArray(raw, view, field, dataOffset, byteLen, elemTypeName, elemSize, elemSigned) {
283
+ if (byteLen === 0)
284
+ return [];
285
+ if (byteLen % elemSize !== 0) {
286
+ throw new TraceFieldDecodeError(`Field "${field.name}" __data_loc length ${byteLen} is not divisible by element size ${elemSize}`, field.name, dataOffset, byteLen, raw.length);
287
+ }
288
+ assertFieldInBounds(field.name, dataOffset, byteLen, raw.length);
289
+ const count = byteLen / elemSize;
290
+ const values = [];
291
+ for (let i = 0; i < count; i++) {
292
+ const elemField = {
293
+ typeName: elemTypeName,
294
+ name: field.name,
295
+ offset: dataOffset + i * elemSize,
296
+ size: elemSize,
297
+ signed: elemSigned,
298
+ };
299
+ values.push(readFieldScalarUnchecked(view, elemField));
300
+ }
301
+ return values;
302
+ }
225
303
  /**
226
304
  * 从 little-endian 原始缓冲区按字段描述读一个标量
227
305
  */
228
- function readFieldScalar(view, field) {
306
+ function readFieldScalarUnchecked(view, field) {
229
307
  const { offset, size, signed, typeName } = field;
230
- if (offset + size > view.byteLength)
231
- return undefined;
232
308
  const t = normalizeType(typeName);
233
309
  if (size === 1) {
234
310
  if (signed || t === "signed char") {
@@ -260,7 +336,14 @@ function readFieldScalar(view, field) {
260
336
  }
261
337
  return view.getBigUint64(offset, true);
262
338
  }
263
- return undefined;
339
+ throw new TraceFieldDecodeError(`Field "${field.name}" has unsupported scalar size ${size}`, field.name, offset, size, view.byteLength);
340
+ }
341
+ /**
342
+ * 从 little-endian 原始缓冲区按字段描述读一个标量;越界时抛出 TraceFieldDecodeError。
343
+ */
344
+ function readFieldScalar(view, field) {
345
+ assertFieldInBounds(field.name, field.offset, field.size, view.byteLength);
346
+ return readFieldScalarUnchecked(view, field);
264
347
  }
265
348
  function parseArrayName(name) {
266
349
  const m = name.match(/^(\w+)\[(\d+)\]$/);
@@ -274,9 +357,10 @@ function readFieldValue(view, field) {
274
357
  return readFieldScalar(view, field);
275
358
  if (arr.len <= 0)
276
359
  return [];
277
- const elemSize = Math.floor(field.size / arr.len);
278
- if (elemSize <= 0)
279
- return undefined;
360
+ if (field.size % arr.len !== 0) {
361
+ throw new TraceFieldDecodeError(`Field "${field.name}" size ${field.size} is not divisible by array length ${arr.len}`, field.name, field.offset, field.size, view.byteLength);
362
+ }
363
+ const elemSize = field.size / arr.len;
280
364
  const values = [];
281
365
  for (let i = 0; i < arr.len; i++) {
282
366
  const elemField = {
@@ -285,10 +369,7 @@ function readFieldValue(view, field) {
285
369
  offset: field.offset + i * elemSize,
286
370
  size: elemSize,
287
371
  };
288
- const v = readFieldScalar(view, elemField);
289
- if (v === undefined)
290
- return undefined;
291
- values.push(v);
372
+ values.push(readFieldScalar(view, elemField));
292
373
  }
293
374
  return values;
294
375
  }
@@ -301,10 +382,9 @@ export function parseCommonFieldsFromRaw(raw, format) {
301
382
  for (const field of format.fields) {
302
383
  if (!field.name.startsWith("common_"))
303
384
  continue;
304
- const v = readFieldScalar(view, field);
305
- if (v !== undefined) {
306
- out[field.name] = v;
307
- }
385
+ if (field.offset + field.size > view.byteLength)
386
+ continue;
387
+ out[field.name] = readFieldScalar(view, field);
308
388
  }
309
389
  return out;
310
390
  }
@@ -318,26 +398,18 @@ export function parseAllFieldsFromRaw(raw, format) {
318
398
  const arr = parseArrayName(field.name);
319
399
  const key = arr ? arr.baseName : field.name;
320
400
  const t = normalizeType(field.typeName);
321
- // __data_loc char[] reason: 实际存一个 u32,低 16 位为 offset,高 16 位为 length
322
- if (t.includes("__data_loc") && t.includes("char")) {
323
- const locField = {
324
- ...field,
325
- typeName: "unsigned int",
326
- signed: false,
327
- size: 4,
328
- };
329
- const loc = readFieldScalar(view, locField);
330
- if (typeof loc === "number") {
331
- const locKey = `__data_loc_${field.name}`;
332
- out[locKey] = loc;
333
- const offset = loc & 0xffff;
334
- const len = (loc >>> 16) & 0xffff;
335
- if (len > 0 && (offset < 0 || offset + len > raw.length)) {
336
- throw new TraceFieldDecodeError(`Field "${field.name}" __data_loc out of bounds: offset=${offset}, len=${len}, raw.length=${raw.length}`, field.name, offset, len, raw.length);
337
- }
338
- if (offset >= 0 && len > 0 && offset + len <= raw.length) {
339
- const bytes = raw.subarray(offset, offset + len);
340
- // 去掉末尾 \0
401
+ if (isDataLocType(field.typeName)) {
402
+ const loc = readLocatorField(view, field);
403
+ const locKey = `__data_loc_${field.name}`;
404
+ out[locKey] = loc;
405
+ const dataOffset = loc & 0xffff;
406
+ const byteLen = (loc >>> 16) & 0xffff;
407
+ if (byteLen > 0 && (dataOffset < 0 || dataOffset + byteLen > raw.length)) {
408
+ throw new TraceFieldDecodeError(`Field "${field.name}" __data_loc out of bounds: offset=${dataOffset}, len=${byteLen}, raw.length=${raw.length}`, field.name, dataOffset, byteLen, raw.length);
409
+ }
410
+ if (t.includes("char")) {
411
+ if (byteLen > 0) {
412
+ const bytes = raw.subarray(dataOffset, dataOffset + byteLen);
341
413
  const nul = bytes.indexOf(0);
342
414
  const slice = nul >= 0 ? bytes.subarray(0, nul) : bytes;
343
415
  out[field.name] = UTF8_DECODER.decode(slice);
@@ -345,17 +417,20 @@ export function parseAllFieldsFromRaw(raw, format) {
345
417
  else {
346
418
  out[field.name] = "";
347
419
  }
420
+ continue;
421
+ }
422
+ const elemInfo = parseDataLocElementType(field.typeName, field.signed);
423
+ if (!elemInfo) {
424
+ throw new TraceFieldDecodeError(`Field "${field.name}" uses unsupported __data_loc element type "${field.typeName}"`, field.name, field.offset, field.size, raw.length);
348
425
  }
426
+ out[key] = readDynamicRegionAsArray(raw, view, field, dataOffset, byteLen, elemInfo.elemTypeName, elemInfo.elemSize, elemInfo.signed);
349
427
  continue;
350
428
  }
351
429
  if (arr && isCharType(field.typeName)) {
352
430
  out[key] = readCharArrayAsString(raw, field.offset, field.size, field.name);
353
431
  continue;
354
432
  }
355
- const v = readFieldValue(view, field);
356
- if (v !== undefined) {
357
- out[key] = v;
358
- }
433
+ out[key] = readFieldValue(view, field);
359
434
  }
360
435
  return out;
361
436
  }
@@ -450,6 +525,9 @@ export function buildTraceParserRegistry(formatTexts, options = {}) {
450
525
  if (typeof src !== "string" && src.transformFieldDict) {
451
526
  transformFieldDictByEventId.set(fmt.eventId, src.transformFieldDict);
452
527
  }
528
+ else {
529
+ transformFieldDictByEventId.delete(fmt.eventId);
530
+ }
453
531
  }
454
532
  });
455
533
  return {
@@ -29,6 +29,10 @@ export declare function tokenizePrintfFormat(printFmt: string): Array<{
29
29
  kind: "spec";
30
30
  spec: PrintfSpec;
31
31
  }>;
32
+ export interface PrintFmtRenderContext {
33
+ eventName?: string;
34
+ eventId?: number;
35
+ }
32
36
  /** 测试专用:重置 printf 解析缓存 */
33
37
  export declare function resetPrintfCachesForTest(): void;
34
38
  /** 测试专用:读取 printf 解析缓存条目数 */
@@ -36,6 +40,6 @@ export declare function getPrintfCacheSizesForTest(): {
36
40
  tokens: number;
37
41
  specs: number;
38
42
  };
39
- export declare function renderPrintFmtAsFieldDict(printFmt: string, printArgs: string[] | undefined, fieldMap: PrintFieldMap): Record<string, string>;
40
- export declare function renderPrintFmt(printFmt: string, printArgs: string[] | undefined, fieldMap: PrintFieldMap): string;
43
+ export declare function renderPrintFmtAsFieldDict(printFmt: string, printArgs: string[] | undefined, fieldMap: PrintFieldMap, context?: PrintFmtRenderContext): Record<string, string>;
44
+ export declare function renderPrintFmt(printFmt: string, printArgs: string[] | undefined, fieldMap: PrintFieldMap, context?: PrintFmtRenderContext): string;
41
45
  export {};
@@ -158,11 +158,21 @@ function formatPrintfUnsignedRadix(unsigned, spec, radix, upper) {
158
158
  : unsigned.toString(radix);
159
159
  body = body.padStart(Math.max(prec, body.length), "0");
160
160
  }
161
- if (hasPrintfFlag(spec.flags, "#") && unsigned !== 0n) {
162
- if (radix === 16)
161
+ if (hasPrintfFlag(spec.flags, "#")) {
162
+ if (radix === 16 && unsigned !== 0n) {
163
163
  prefix = upper ? "0X" : "0x";
164
- else if (radix === 8 && body !== "0" && !body.startsWith("0"))
165
- prefix = "0";
164
+ }
165
+ else if (radix === 8) {
166
+ if (unsigned === 0n && (prec === 0 || body === "")) {
167
+ body = "0";
168
+ }
169
+ else if (body === "") {
170
+ body = "0";
171
+ }
172
+ else if (!body.startsWith("0")) {
173
+ body = "0" + body;
174
+ }
175
+ }
166
176
  }
167
177
  const zeroPad = hasPrintfFlag(spec.flags, "0") &&
168
178
  !hasPrintfFlag(spec.flags, "-") &&
@@ -333,6 +343,16 @@ export function tokenizePrintfFormat(printFmt) {
333
343
  flushText();
334
344
  return out;
335
345
  }
346
+ function assertPrintArgCount(printFmt, printArgs, context) {
347
+ const specCount = extractPrintfSpecs(printFmt).length;
348
+ const argCount = printArgs?.length ?? 0;
349
+ if (argCount < specCount) {
350
+ const eventLabel = context?.eventName !== undefined
351
+ ? `"${context.eventName}"${context.eventId !== undefined ? ` (ID ${context.eventId})` : ""}`
352
+ : "unknown event";
353
+ throw new Error(`Print fmt for event ${eventLabel} requires ${specCount} args but got ${argCount} at spec index ${argCount}: "${printFmt}"`);
354
+ }
355
+ }
336
356
  function extractPrintfSpecs(printFmt) {
337
357
  const cached = PRINT_FMT_SPEC_CACHE.get(printFmt);
338
358
  if (cached)
@@ -401,12 +421,20 @@ function evalPrintArgExpr(exprRaw, fieldMap) {
401
421
  return v;
402
422
  throw new Error(`__get_str(${m[1]}) requires a string field, got ${typeof v}`);
403
423
  }
404
- m = expr.match(/^__get_dynamic_array\((\w+)\)$/);
424
+ m = expr.match(/^__get_dynamic_array\((\w+)\)(?:\[(\d+)])?$/);
405
425
  if (m) {
406
426
  const v = fieldMap[m[1]];
407
- if (Array.isArray(v))
408
- return v;
409
- throw new Error(`__get_dynamic_array(${m[1]}) requires an array field, got ${typeof v}`);
427
+ if (!Array.isArray(v)) {
428
+ throw new Error(`__get_dynamic_array(${m[1]}) requires an array field, got ${typeof v}`);
429
+ }
430
+ if (m[2] !== undefined) {
431
+ const idx = parseInt(m[2], 10);
432
+ if (idx < 0 || idx >= v.length) {
433
+ throw new Error(`Invalid print arg index for __get_dynamic_array(${m[1]})[${m[2]}]`);
434
+ }
435
+ return v[idx];
436
+ }
437
+ return v;
410
438
  }
411
439
  m = expr.match(/^REC->(\w+)&0xffff$/);
412
440
  if (m) {
@@ -481,9 +509,9 @@ function fieldKeyFromNormalizedArg(normalizedArg, index) {
481
509
  if (mGetStr) {
482
510
  return mGetStr[1];
483
511
  }
484
- const mGetArr = k.match(/^__get_dynamic_array\((\w+)\)$/);
512
+ const mGetArr = k.match(/^__get_dynamic_array\((\w+)\)(?:\[(\d+)])?$/);
485
513
  if (mGetArr) {
486
- return mGetArr[1];
514
+ return mGetArr[2] !== undefined ? `${mGetArr[1]}__${mGetArr[2]}` : mGetArr[1];
487
515
  }
488
516
  // REC->__data_loc_path&0xffff 在 fieldDict 中收敛为 path 作为键名
489
517
  const mDataLocOff = k.match(/^__data_loc_(\w+)&0xffff$/);
@@ -492,7 +520,8 @@ function fieldKeyFromNormalizedArg(normalizedArg, index) {
492
520
  }
493
521
  return k.length > 0 ? k : `arg${index}`;
494
522
  }
495
- export function renderPrintFmtAsFieldDict(printFmt, printArgs, fieldMap) {
523
+ export function renderPrintFmtAsFieldDict(printFmt, printArgs, fieldMap, context) {
524
+ assertPrintArgCount(printFmt, printArgs, context);
496
525
  const { normalizedArgs, values } = buildPrintArgValues(printArgs, fieldMap);
497
526
  const specs = extractPrintfSpecs(printFmt);
498
527
  const out = {};
@@ -506,7 +535,8 @@ export function renderPrintFmtAsFieldDict(printFmt, printArgs, fieldMap) {
506
535
  }
507
536
  return out;
508
537
  }
509
- export function renderPrintFmt(printFmt, printArgs, fieldMap) {
538
+ export function renderPrintFmt(printFmt, printArgs, fieldMap, context) {
539
+ assertPrintArgCount(printFmt, printArgs, context);
510
540
  const { normalizedArgs, values } = buildPrintArgValues(printArgs, fieldMap);
511
541
  const tokens = getCachedPrintfTokens(printFmt);
512
542
  let argIdx = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hm-pt-core",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "DFX perf trace shared types and trace format decode primitives",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",