diff --git a/package.json b/package.json index 68d695e..efb9dc5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ton-assembly", - "version": "0.2.4", + "version": "0.3.0", "description": "TON assembler and disassembler", "keywords": [ "ton", diff --git a/src/coverage/data.ts b/src/coverage/data.ts index d8fa4e4..5ad5266 100644 --- a/src/coverage/data.ts +++ b/src/coverage/data.ts @@ -95,8 +95,8 @@ export const buildFuncLineInfo = (traces: TraceInfo[], funcCode: string): Line[] for (const trace of traces) { const perLineSteps: Map = new Map() for (const step of trace.steps) { - if (step.funcLoc === undefined) continue - const line = step.funcLoc.line + if (step.sourceMapEntries.length === 0) continue + const line = step.sourceMapEntries[0]?.loc.line ?? 0 perLineSteps.set(line, [...(perLineSteps.get(line) ?? []), step]) } diff --git a/src/coverage/index.ts b/src/coverage/index.ts index 02db775..12610a7 100644 --- a/src/coverage/index.ts +++ b/src/coverage/index.ts @@ -2,7 +2,7 @@ import type {Cell} from "@ton/core" import type { Mapping} from "../runtime"; import {compileCellWithMapping, decompileCell} from "../runtime" import {print, parse} from "../text" -import {createMappingInfo, createTraceInfoPerTransaction, loadFuncMapping} from "../trace" +import {createMappingInfo, createTraceInfoPerTransaction} from "../trace" import {buildFuncLineInfo, buildLineInfo, generateCoverageSummary} from "./data" import {readFileSync} from "node:fs" @@ -22,13 +22,13 @@ export function collectFuncCoverage( cell: Cell, logs: string, funcSources: string, - funcMappingPath: string, + _funcMappingPath: string, ) { const [_, mapping] = recompileCell(cell, true) const info = createMappingInfo(mapping) - const funcMapping = loadFuncMapping(readFileSync(funcMappingPath, "utf8")) - const traceInfos = createTraceInfoPerTransaction(logs, info, funcMapping) + // const funcMapping = loadFuncMapping(readFileSync(funcMappingPath, "utf8")) + const traceInfos = createTraceInfoPerTransaction(logs, info, undefined) const func = readFileSync(funcSources, "utf8") const combinedLines = buildFuncLineInfo(traceInfos, func) const combinedSummary = generateCoverageSummary(combinedLines) diff --git a/src/coverage/test/__snapshots__/asm-coverage.spec.ts.snap b/src/coverage/test/__snapshots__/asm-coverage.spec.ts.snap index 2b9abc5..06e5dbc 100644 --- a/src/coverage/test/__snapshots__/asm-coverage.spec.ts.snap +++ b/src/coverage/test/__snapshots__/asm-coverage.spec.ts.snap @@ -3,20 +3,20 @@ exports[`asm coverage dictionary 1`] = ` "Coverage Summary: Lines: 4/8 (50.00%) -Total Gas: 296 -Total Hits: 4 +Total Gas: 301 +Total Hits: 5 Instruction Stats: - DICTIGETJMPZ | 226 gas | 1 hits | 226 avg gas | 76.35% - DICTPUSHCONST | 34 gas | 1 hits | 34 avg gas | 11.49% - PUSHINT_4 | 18 gas | 1 hits | 18 avg gas | 6.08% - INC | 18 gas | 1 hits | 18 avg gas | 6.08% + DICTIGETJMPZ | 226 gas | 1 hits | 226 avg gas | 75.08% + DICTPUSHCONST | 34 gas | 1 hits | 34 avg gas | 11.30% + INC | 23 gas | 2 hits | 11.5 avg gas | 7.64% + PUSHINT_4 | 18 gas | 1 hits | 18 avg gas | 5.98% Annotated Code: 1 ✓ | DICTPUSHCONST 19 [ | gas:34 | hits:1 2 | 0 => { | | 3 ✓ | PUSHINT_4 10 | gas:18 | hits:1 - 4 ✓ | INC | gas:18 | hits:1 + 4 ✓ | INC | gas:23 | hits:2 5 | } | | 6 | 2 => { | | 7 ✗ | PUSHINT_4 5 | | @@ -31,14 +31,14 @@ Annotated Code: exports[`asm coverage dictionary 2 1`] = ` "Coverage Summary: Lines: 4/8 (50.00%) -Total Gas: 296 -Total Hits: 4 +Total Gas: 301 +Total Hits: 5 Instruction Stats: - DICTIGETJMPZ | 226 gas | 1 hits | 226 avg gas | 76.35% - DICTPUSHCONST | 34 gas | 1 hits | 34 avg gas | 11.49% - PUSHINT_4 | 18 gas | 1 hits | 18 avg gas | 6.08% - INC | 18 gas | 1 hits | 18 avg gas | 6.08% + DICTIGETJMPZ | 226 gas | 1 hits | 226 avg gas | 75.08% + DICTPUSHCONST | 34 gas | 1 hits | 34 avg gas | 11.30% + INC | 23 gas | 2 hits | 11.5 avg gas | 7.64% + PUSHINT_4 | 18 gas | 1 hits | 18 avg gas | 5.98% Annotated Code: 1 ✓ | DICTPUSHCONST 19 [ | gas:34 | hits:1 @@ -48,7 +48,7 @@ Annotated Code: 5 | } | | 6 | 2 => { | | 7 ✓ | PUSHINT_4 5 | gas:18 | hits:1 - 8 ✓ | INC | gas:18 | hits:1 + 8 ✓ | INC | gas:23 | hits:2 9 | } | | 10 ✗ | ] | | 11 ✓ | DICTIGETJMPZ | gas:226 | hits:1 @@ -80,15 +80,15 @@ Annotated Code: exports[`asm coverage nested try with rethrow 1`] = ` "Coverage Summary: Lines: 9/9 (100.00%) -Total Gas: 334 -Total Hits: 9 +Total Gas: 344 +Total Hits: 11 Instruction Stats: - PUSHCONT | 104 gas | 4 hits | 26 avg gas | 31.14% - THROW | 84 gas | 1 hits | 84 avg gas | 25.15% - THROWANY | 76 gas | 1 hits | 76 avg gas | 22.75% - TRY | 52 gas | 2 hits | 26 avg gas | 15.57% - PUSHINT_4 | 18 gas | 1 hits | 18 avg gas | 5.39% + PUSHCONT | 104 gas | 4 hits | 26 avg gas | 30.23% + THROW | 84 gas | 1 hits | 84 avg gas | 24.42% + THROWANY | 76 gas | 1 hits | 76 avg gas | 22.09% + TRY | 52 gas | 2 hits | 26 avg gas | 15.12% + PUSHINT_4 | 28 gas | 3 hits | 9.33 avg gas | 8.14% Annotated Code: 1 ✓ | PUSHCONT { | gas:26 | hits:1 @@ -101,7 +101,7 @@ Annotated Code: 8 ✓ | TRY | gas:26 | hits:1 9 | } | | 10 ✓ | PUSHCONT { | gas:26 | hits:1 -11 ✓ | PUSHINT_4 2 | gas:18 | hits:1 +11 ✓ | PUSHINT_4 2 | gas:28 | hits:3 12 | } | | 13 ✓ | TRY | gas:26 | hits:1 14 | | |" @@ -110,13 +110,13 @@ Annotated Code: exports[`asm coverage simple if 1`] = ` "Coverage Summary: Lines: 4/7 (57.14%) -Total Gas: 80 -Total Hits: 4 +Total Gas: 85 +Total Hits: 5 Instruction Stats: - PUSHINT_4 | 36 gas | 2 hits | 18 avg gas | 45.00% - PUSHCONT | 26 gas | 1 hits | 26 avg gas | 32.50% - IF | 18 gas | 1 hits | 18 avg gas | 22.50% + PUSHINT_4 | 36 gas | 2 hits | 18 avg gas | 42.35% + PUSHCONT | 26 gas | 1 hits | 26 avg gas | 30.59% + IF | 23 gas | 2 hits | 11.5 avg gas | 27.06% Annotated Code: 1 ✓ | PUSHINT_4 0 | gas:18 | hits:1 @@ -126,27 +126,27 @@ Annotated Code: 5 ✗ | INC | | 6 ✗ | INC | | 7 | } | | -8 ✓ | IF | gas:18 | hits:1 +8 ✓ | IF | gas:23 | hits:2 9 | | |" `; exports[`asm coverage simple if-else 1`] = ` "Coverage Summary: Lines: 6/7 (85.71%) -Total Gas: 124 -Total Hits: 6 +Total Gas: 134 +Total Hits: 8 Instruction Stats: - PUSHCONT | 52 gas | 2 hits | 26 avg gas | 41.94% - PUSHINT_4 | 36 gas | 2 hits | 18 avg gas | 29.03% - INC | 18 gas | 1 hits | 18 avg gas | 14.52% - IFELSE | 18 gas | 1 hits | 18 avg gas | 14.52% + PUSHCONT | 52 gas | 2 hits | 26 avg gas | 38.81% + PUSHINT_4 | 36 gas | 2 hits | 18 avg gas | 26.87% + INC | 28 gas | 3 hits | 9.33 avg gas | 20.90% + IFELSE | 18 gas | 1 hits | 18 avg gas | 13.43% Annotated Code: 1 ✓ | PUSHINT_4 0 | gas:18 | hits:1 2 ✓ | PUSHINT_4 -1 | gas:18 | hits:1 3 ✓ | PUSHCONT { | gas:26 | hits:1 - 4 ✓ | INC | gas:18 | hits:1 + 4 ✓ | INC | gas:28 | hits:3 5 | } | | 6 ✓ | PUSHCONT { | gas:26 | hits:1 7 ✗ | DEC | | @@ -158,15 +158,15 @@ Annotated Code: exports[`asm coverage try with throw 1`] = ` "Coverage Summary: Lines: 6/6 (100.00%) -Total Gas: 198 -Total Hits: 6 +Total Gas: 208 +Total Hits: 8 Instruction Stats: - THROW | 84 gas | 1 hits | 84 avg gas | 42.42% - PUSHCONT | 52 gas | 2 hits | 26 avg gas | 26.26% - TRY | 26 gas | 1 hits | 26 avg gas | 13.13% - PUSHINT_4 | 18 gas | 1 hits | 18 avg gas | 9.09% - SUB | 18 gas | 1 hits | 18 avg gas | 9.09% + THROW | 84 gas | 1 hits | 84 avg gas | 40.38% + PUSHCONT | 52 gas | 2 hits | 26 avg gas | 25.00% + SUB | 28 gas | 3 hits | 9.33 avg gas | 13.46% + TRY | 26 gas | 1 hits | 26 avg gas | 12.50% + PUSHINT_4 | 18 gas | 1 hits | 18 avg gas | 8.65% Annotated Code: 1 ✓ | PUSHINT_4 10 | gas:18 | hits:1 @@ -174,7 +174,7 @@ Annotated Code: 3 ✓ | THROW 10 | gas:84 | hits:1 4 | } | | 5 ✓ | PUSHCONT { | gas:26 | hits:1 -6 ✓ | SUB | gas:18 | hits:1 +6 ✓ | SUB | gas:28 | hits:3 7 | } | | 8 ✓ | TRY | gas:26 | hits:1 9 | | |" @@ -183,19 +183,19 @@ Annotated Code: exports[`asm coverage try without throw 1`] = ` "Coverage Summary: Lines: 5/6 (83.33%) -Total Gas: 114 -Total Hits: 5 +Total Gas: 124 +Total Hits: 7 Instruction Stats: - PUSHCONT | 52 gas | 2 hits | 26 avg gas | 45.61% - TRY | 26 gas | 1 hits | 26 avg gas | 22.81% - PUSHINT_4 | 18 gas | 1 hits | 18 avg gas | 15.79% - INC | 18 gas | 1 hits | 18 avg gas | 15.79% + PUSHCONT | 52 gas | 2 hits | 26 avg gas | 41.94% + INC | 28 gas | 3 hits | 9.33 avg gas | 22.58% + TRY | 26 gas | 1 hits | 26 avg gas | 20.97% + PUSHINT_4 | 18 gas | 1 hits | 18 avg gas | 14.52% Annotated Code: 1 ✓ | PUSHINT_4 10 | gas:18 | hits:1 2 ✓ | PUSHCONT { | gas:26 | hits:1 -3 ✓ | INC | gas:18 | hits:1 +3 ✓ | INC | gas:28 | hits:3 4 | } | | 5 ✓ | PUSHCONT { | gas:26 | hits:1 6 ✗ | DEC | | @@ -207,30 +207,30 @@ Annotated Code: exports[`asm coverage while loop with break 1`] = ` "Coverage Summary: Lines: 10/10 (100.00%) -Total Gas: 1002 -Total Hits: 45 +Total Gas: 1067 +Total Hits: 58 Instruction Stats: - DUP | 252 gas | 14 hits | 18 avg gas | 25.15% - GTINT | 182 gas | 7 hits | 26 avg gas | 18.16% - LESSINT | 182 gas | 7 hits | 26 avg gas | 18.16% - IFRETALT | 182 gas | 7 hits | 26 avg gas | 18.16% - DEC | 108 gas | 6 hits | 18 avg gas | 10.78% - PUSHCONT | 52 gas | 2 hits | 26 avg gas | 5.19% - WHILEBRK | 26 gas | 1 hits | 26 avg gas | 2.59% - PUSHINT_4 | 18 gas | 1 hits | 18 avg gas | 1.80% + DUP | 252 gas | 14 hits | 18 avg gas | 23.62% + GTINT | 217 gas | 14 hits | 15.5 avg gas | 20.34% + LESSINT | 182 gas | 7 hits | 26 avg gas | 17.06% + IFRETALT | 182 gas | 7 hits | 26 avg gas | 17.06% + DEC | 138 gas | 12 hits | 11.5 avg gas | 12.93% + PUSHCONT | 52 gas | 2 hits | 26 avg gas | 4.87% + WHILEBRK | 26 gas | 1 hits | 26 avg gas | 2.44% + PUSHINT_4 | 18 gas | 1 hits | 18 avg gas | 1.69% Annotated Code: 1 ✓ | PUSHINT_4 10 | gas:18 | hits:1 2 ✓ | PUSHCONT { | gas:26 | hits:1 3 ✓ | DUP | gas:126 | hits:7 - 4 ✓ | GTINT 0 | gas:182 | hits:7 + 4 ✓ | GTINT 0 | gas:217 | hits:14 5 | } | | 6 ✓ | PUSHCONT { | gas:26 | hits:1 7 ✓ | DUP | gas:126 | hits:7 8 ✓ | LESSINT 5 | gas:182 | hits:7 9 ✓ | IFRETALT | gas:182 | hits:7 -10 ✓ | DEC | gas:108 | hits:6 +10 ✓ | DEC | gas:138 | hits:12 11 | } | | 12 ✓ | WHILEBRK | gas:26 | hits:1 13 | | |" diff --git a/src/runtime/builder.ts b/src/runtime/builder.ts index fcbf572..24b4b95 100644 --- a/src/runtime/builder.ts +++ b/src/runtime/builder.ts @@ -7,9 +7,9 @@ import type {Dictionary, DictionaryKeyTypes} from "../dict/Dictionary" * Describes an instruction with its offset in the parent `Cell`. */ export type InstructionWithOffset = { - instr: Instr - offset: number - debugSection: number + readonly instr: Instr + readonly offset: number + readonly debugSections: readonly number[] } /** @@ -22,21 +22,21 @@ export type Mapping = { /** * The hash of the `Cell` that is being mapped. */ - cell: string + readonly cell: string /** * The instructions that are stored in the `Cell`. */ - instructions: InstructionWithOffset[] + readonly instructions: InstructionWithOffset[] /** * Instructions can store references to other cells. * These references are stored in this array. */ - subMappings: Mapping[] + readonly subMappings: readonly Mapping[] /** * When we serialize a `Dictionary`, we store additional information * about the position of the cell in the dictionary Cell. */ - dictionaryInfo: DictionaryInfo[] + readonly dictionaryInfo: readonly DictionaryInfo[] } /** @@ -53,15 +53,15 @@ export type DictionaryInfo = { /** * The `CodeBuilder` that builds the Dictionary Cell. */ - builder: CodeBuilder + readonly builder: CodeBuilder /** * The offset of the Cell with instructions in the Dictionary Cell. */ - offset: number + readonly offset: number /** * The `Cell` that contains the instructions. */ - childCell: Cell + readonly childCell: Cell } /** @@ -71,7 +71,7 @@ export class CodeBuilder extends Builder { private readonly instructions: InstructionWithOffset[] = [] private readonly subMappings: Mapping[] = [] private readonly dictionaryInfo: DictionaryInfo[] = [] - private debugSectionId: number = -1 + private debugSectionIds: number[] = [] public constructor( public readonly isDictionaryCell: boolean = false, @@ -81,10 +81,26 @@ export class CodeBuilder extends Builder { } public storeInstructionPrefix(value: bigint | number, bits: number, instr: Instr): this { - this.instructions.push({instr, offset: this.bits, debugSection: this.debugSectionId}) + this.instructions.push({instr, offset: this.bits, debugSections: [...this.debugSectionIds]}) + this.debugSectionIds = [] return super.storeUint(value, bits) } + public addImplicitRet() { + const lastInstruction = this.instructions.at(-1) + if (!lastInstruction) return + + // This implicit RET instruction is used as an anchor for all trailing DEBUGMARK instructions. + this.instructions.push({ + instr: { + $: "RET", + loc: lastInstruction.instr.loc, + }, + offset: lastInstruction.offset, + debugSections: [...this.debugSectionIds], + }) + } + public build(): [Cell, Mapping] { const cell = this.asCell() return [ @@ -98,8 +114,13 @@ export class CodeBuilder extends Builder { ] } + public clearDebugSectionIds(): this { + this.debugSectionIds = [] + return this + } + public startDebugSection(id: number): this { - this.debugSectionId = id + this.debugSectionIds.push(id) return this } @@ -147,7 +168,7 @@ export class CodeBuilder extends Builder { this.instructions.push(...other.instructions) this.subMappings.push(...other.subMappings) this.dictionaryInfo.push(...other.dictionaryInfo) - this.debugSectionId = other.debugSectionId + this.debugSectionIds = [...other.debugSectionIds] return this } } diff --git a/src/runtime/instr.ts b/src/runtime/instr.ts index e682bc3..9abe4b2 100644 --- a/src/runtime/instr.ts +++ b/src/runtime/instr.ts @@ -67,6 +67,8 @@ export const codeType = (): $.Type => { }, store(b, t, options) { compileInstructions(b, t, options) + b.addImplicitRet() + b.clearDebugSectionIds() }, } } diff --git a/src/runtime/util.ts b/src/runtime/util.ts index 0e4d42e..3732017 100644 --- a/src/runtime/util.ts +++ b/src/runtime/util.ts @@ -85,10 +85,10 @@ export const decompiledCode = (instructions: Instr[]): Instructions => ({ const processMappingInstructions = (mapping: Mapping, b: CodeBuilder) => mapping.instructions.map( - ({instr, offset, debugSection}): InstructionWithOffset => ({ + ({instr, offset, debugSections}): InstructionWithOffset => ({ instr, offset: offset + b.bits, - debugSection, + debugSections, }), ) @@ -600,6 +600,7 @@ export const PSEUDO_PUSHREF: Type = { if (options.skipRefs) { // compile instructions without ref at all in place compileInstructions(b, val.arg0.instructions, options) + b.clearDebugSectionIds() return } PSEUDO_PUSHREF_ALWAYS.store(b, val, options) @@ -621,7 +622,7 @@ export const PSEUDO_PUSHREF_ALWAYS: Type = { mapping.instructions.splice(0, 0, { offset: 0, instr: JMPREF(val.arg0, val.loc), - debugSection: -1, + debugSections: [], }) b.storeRefWithMapping([cell, mapping]) diff --git a/src/trace/high-level-source-map.ts b/src/trace/high-level-source-map.ts new file mode 100644 index 0000000..75da0b6 --- /dev/null +++ b/src/trace/high-level-source-map.ts @@ -0,0 +1,76 @@ +enum FunctionInlineMode { + NotCalculated = 0, + InlineViaFif = 1, + InlineRef = 2, + InlineInPlace = 3, + NoInline = 4, +} + +export type HighLevelSourceMapLocation = { + readonly file: string + readonly line: number + readonly col: number + readonly line_offset: number + readonly length: number +} + +export type HighLevelSourceMapEntryContext = { + readonly descr?: string + readonly is_entry?: boolean + readonly ast_kind: string + readonly func_name: string + readonly inlined_to_func?: string + readonly func_inline_mode: FunctionInlineMode + readonly before_inlined_function_call?: boolean + readonly after_inlined_function_call?: boolean +} + +export type HighLevelSourceMapDebugInfo = { + readonly opcode?: string + readonly line_str?: string + readonly line_off?: string +} + +export type HighLevelSourceMapEntry = { + readonly idx: number + readonly loc: HighLevelSourceMapLocation + readonly vars: readonly HighLevelSourceMapVariable[] + readonly context: HighLevelSourceMapEntryContext + readonly debug?: HighLevelSourceMapDebugInfo +} + +export type HighLevelSourceMapVariable = { + readonly name: string + readonly type: string + readonly constant_value?: string + readonly possible_qualifier_types: readonly string[] +} + +export type HighLevelSourceMapGlobalVariable = { + readonly name: string + readonly type: string +} + +export type HighLevelSourceMapFile = { + readonly path: string + readonly is_stdlib: boolean + readonly content: string +} + +/** + * Represents a high-level source map. + * "High-level" in this case means that this source map only contains a mapping from DEBUGMARK ID to + * high-level language code, but it does not contain any further mapping from DEBUGMARK ID to + * specific instructions in bitcode. + */ +export type HighLevelSourceMap = { + readonly version: string + readonly files: readonly HighLevelSourceMapFile[] + readonly globals: readonly HighLevelSourceMapGlobalVariable[] + readonly locations: readonly HighLevelSourceMapEntry[] + readonly debugCode64?: string +} + +export const loadSourceMap = (content: string): HighLevelSourceMap => { + return JSON.parse(content) as HighLevelSourceMap +} diff --git a/src/trace/index.ts b/src/trace/index.ts index 0477a2d..42add64 100644 --- a/src/trace/index.ts +++ b/src/trace/index.ts @@ -2,3 +2,4 @@ export * from "./func-mapping" export * from "./logs" export * from "./mapping" export * from "./trace" +export * from "./high-level-source-map" diff --git a/src/trace/logs.ts b/src/trace/logs.ts index f4d54b3..9136f8a 100644 --- a/src/trace/logs.ts +++ b/src/trace/logs.ts @@ -25,14 +25,29 @@ export function parseLogs(log: string): LogEntry[][] { let currentStack: StackElement[] = [] let currentGas: number = 1_000_000 + const callStack: string[] = [] + for (const vmLine of vmLines) { if (vmLine.$ === "VmStack") { currentStack = vmLine.stack } if (vmLine.$ === "VmLoc") { + const cellHash = vmLine.hash.toLowerCase() + + if (callStack.length === 0) { + // first frame + callStack.push(cellHash) + } else if (callStack.length > 1 && callStack.at(-2) === cellHash) { + // return to the previous frame + callStack.pop() + } else if (callStack.at(-1) !== cellHash) { + // new frame + callStack.push(cellHash) + } + entries.push({ - hash: vmLine.hash.toLowerCase(), + hash: cellHash, offset: vmLine.offset, stack: currentStack, gas: currentGas, @@ -45,12 +60,21 @@ export function parseLogs(log: string): LogEntry[][] { if (vmLine.$ === "VmExecute" && vmLine.instr === "implicit RET") { const lastEntry = entries.at(-1) if (lastEntry) { + // Two subsequent RET share the same location, and this is actually wrong, + // so we manually set the correct hash by callstack + const actualHash = + lastEntry.implicit && callStack.length > 0 + ? (callStack.at(-1) ?? lastEntry.hash) + : lastEntry.hash + entries.push({ ...lastEntry, + hash: actualHash, implicit: true, gasCost: 5, }) } + callStack.pop() } if (vmLine.$ === "VmGasRemaining") { diff --git a/src/trace/mapping.ts b/src/trace/mapping.ts index 2e583aa..6a9bb52 100644 --- a/src/trace/mapping.ts +++ b/src/trace/mapping.ts @@ -44,14 +44,14 @@ export type InstructionInfo = { readonly offset: number /** - * Debug section number. + * Debug section numbers. * * Debug sections are used to group instructions together in the code. * This way we later can match several instructions to a single statement in the source code. * - * If instruction is not part of any debug section, this value is -1. + * If instruction is not part of any debug section, this value is an empty array. */ - readonly debugSection: number + readonly debugSections: readonly number[] } export type CellHash = string @@ -120,11 +120,11 @@ const processMapping = (mapping: Mapping, cells: CellsMapping) => { } const instructions = mapping.instructions.map( - ({instr: {$: name, loc}, offset, debugSection}): InstructionInfo => ({ + ({instr: {$: name, loc}, offset, debugSections}): InstructionInfo => ({ name, loc: loc ? fromParserLoc(loc) : undefined, offset, - debugSection, + debugSections, }), ) diff --git a/src/trace/trace.ts b/src/trace/trace.ts index 414aa4c..ac9bacc 100644 --- a/src/trace/trace.ts +++ b/src/trace/trace.ts @@ -1,7 +1,7 @@ import type {InstructionInfo, Loc, MappingInfo} from "./mapping" import type {LogEntry, StackElement} from "./logs" import {parseLogs} from "./logs" -import type {FuncSourceLoc, FuncMapping} from "./func-mapping" +import type {HighLevelSourceMap, HighLevelSourceMapEntry} from "./high-level-source-map" /** * Describes a single step in the trace. @@ -12,7 +12,7 @@ export type Step = { readonly stack: readonly StackElement[] readonly gas: number readonly gasCost: number - readonly funcLoc: undefined | FuncSourceLoc + readonly sourceMapEntries: readonly HighLevelSourceMapEntry[] } /** @@ -28,7 +28,7 @@ export type TraceInfo = { export const createTraceInfo = ( logs: string, mapping: MappingInfo, - funcMapping: undefined | FuncMapping, + sourceMap: undefined | HighLevelSourceMap, ): TraceInfo => { const stepLogInfo = parseLogs(logs).flat() @@ -44,10 +44,9 @@ export const createTraceInfo = ( const instr = instructions[index] if (!instr) return [] - const funcLoc = - instr.debugSection !== -1 && funcMapping - ? funcMapping.locations[instr.debugSection] - : undefined + const sourceMapEntries = sourceMap + ? instr.debugSections.map(it => sourceMap.locations[it]).filter(it => it !== undefined) + : [] return [ { @@ -56,7 +55,7 @@ export const createTraceInfo = ( stack: stepInfo.stack, gas: stepInfo.gas, gasCost: stepInfo.gasCost, - funcLoc, + sourceMapEntries, }, ] }) @@ -67,7 +66,7 @@ export const createTraceInfo = ( export const createTraceInfoPerTransaction = ( logs: string, mapping: MappingInfo, - funcMapping: undefined | FuncMapping, + sourceMap: undefined | HighLevelSourceMap, ): TraceInfo[] => { const transactionLogs = parseLogs(logs) @@ -101,19 +100,33 @@ export const createTraceInfoPerTransaction = ( offsetZeroCount = 1 } - const funcLoc = - instr.debugSection !== -1 && funcMapping - ? funcMapping.locations[instr.debugSection] - : undefined + const sourceMapEntries = sourceMap + ? instr.debugSections + .map(it => sourceMap.locations[it]) + .filter(it => it !== undefined) + : [] if (stepInfo.implicit) { + const actualInstr = + index === -1 && instructions.at(-1)?.name === "RET" + ? (instructions.at(-1) ?? instr) + : instructions.length > index + 1 && instructions[index + 1]?.name === "RET" + ? (instructions[index + 1] ?? instr) + : instr + + const actualSourceMapEntries = sourceMap + ? actualInstr.debugSections + .map(it => sourceMap.locations[it]) + .filter(it => it !== undefined) + : [] + steps.push({ - loc: undefined, + loc: actualInstr === instr ? undefined : actualInstr.loc, instructionName: "implicit RET", stack: stepInfo.stack, gas: stepInfo.gas, gasCost: stepInfo.gasCost, - funcLoc: funcLoc, + sourceMapEntries: actualSourceMapEntries, }) continue } @@ -124,7 +137,7 @@ export const createTraceInfoPerTransaction = ( stack: stepInfo.stack, gas: stepInfo.gas, gasCost: stepInfo.gasCost, - funcLoc: funcLoc, + sourceMapEntries, }) }