@tact-lang/opcode
Advanced tools
Comparing version 0.0.9 to 0.0.10
@@ -8,2 +8,10 @@ # Changelog | ||
## [0.0.10] - 2023-03-27 | ||
## Fixed | ||
- Fix opcode offsets and source cell hash in `PUSHCONT` opcode that could lead to broken code coverage | ||
## Changed | ||
- Upgrade to `ton-core@0.49.0` | ||
## [0.0.9] - 2023-03-24 | ||
@@ -10,0 +18,0 @@ - Fix oppcode length in shifted cells |
@@ -115,6 +115,6 @@ import { Cell } from 'ton-core'; | ||
code: 'PUSHSLICE'; | ||
args: [Cell]; | ||
args: [Cell, number, number, number, number]; | ||
} | { | ||
code: 'PUSHCONT'; | ||
args: [Cell]; | ||
args: [Cell, number, number, number, number]; | ||
} | { | ||
@@ -185,3 +185,3 @@ code: 'ADDCONST'; | ||
code: 'STSLICECONST'; | ||
args: [Cell]; | ||
args: [Cell, number, number, number, number]; | ||
} | { | ||
@@ -188,0 +188,0 @@ code: 'LDI'; |
@@ -6,10 +6,2 @@ "use strict"; | ||
const Codepage_1 = require("./Codepage"); | ||
function fetchSubslice(slice, bits, refs) { | ||
let b = (0, ton_core_1.beginCell)() | ||
.storeBits(slice.loadBits(bits)); | ||
for (let i = 0; i < (refs || 0); i++) { | ||
b.storeRef(slice.loadRef()); | ||
} | ||
return b.asCell(); | ||
} | ||
const CP0Auto = new Codepage_1.Codepage(); | ||
@@ -286,17 +278,24 @@ exports.CP0Auto = CP0Auto; | ||
let x = slice.loadUint(4); | ||
let len = 8 * x + 4; | ||
let subslice = fetchSubslice(slice, len); | ||
return { code: 'PUSHSLICE', args: [subslice] }; | ||
let bits = 8 * x + 4; | ||
let refs = 0; | ||
let bitsOffset = slice.offsetBits; | ||
let refsOffset = slice.offsetRefs; | ||
let cell = loadSubslice(slice, bits, refs); | ||
return { code: 'PUSHSLICE', args: [cell, bitsOffset, refsOffset, bits, refs] }; | ||
}); | ||
CP0Auto.insertHex('8c', 8, (slice) => { | ||
let r = slice.loadUint(2) + 1; | ||
let xx = slice.loadUint(5); | ||
let subslice = fetchSubslice(slice, 8 * xx + 1, r); | ||
return { code: 'PUSHSLICE', args: [subslice] }; | ||
let refs = slice.loadUint(2) + 1; | ||
let bits = (8 * slice.loadUint(5) + 1); | ||
let bitsOffset = slice.offsetBits; | ||
let refsOffset = slice.offsetRefs; | ||
let cell = loadSubslice(slice, bits, refs); | ||
return { code: 'PUSHSLICE', args: [cell, bitsOffset, refsOffset, bits, refs] }; | ||
}); | ||
CP0Auto.insertHex('8d', 8, (slice) => { | ||
let r = slice.loadUint(3); | ||
let xx = slice.loadUint(7); | ||
let subslice = fetchSubslice(slice, 8 * xx + 6, r); | ||
return { code: 'PUSHSLICE', args: [subslice] }; | ||
let refs = slice.loadUint(3); | ||
let bits = 8 * slice.loadUint(7) + 6; | ||
let bitsOffset = slice.offsetBits; | ||
let refsOffset = slice.offsetRefs; | ||
let cell = loadSubslice(slice, bits, refs); | ||
return { code: 'PUSHSLICE', args: [cell, bitsOffset, refsOffset, bits, refs] }; | ||
}); | ||
@@ -307,10 +306,15 @@ // 9281536 (DUMMY) | ||
let refs = (args >> 7) & 3; | ||
let dataBytes = (args & 127) * 8; | ||
let subslice = fetchSubslice(slice, dataBytes, refs); | ||
return { code: 'PUSHCONT', args: [subslice] }; | ||
let bits = (args & 127) * 8; | ||
let bitsOffset = slice.offsetBits; | ||
let refsOffset = slice.offsetRefs; | ||
let cell = loadSubslice(slice, bits, refs); | ||
return { code: 'PUSHCONT', args: [cell, bitsOffset, refsOffset, bits, refs] }; | ||
}); | ||
CP0Auto.insertHex('9', 4, (slice) => { | ||
let len = slice.loadUint(4) * 8; | ||
let subslice = fetchSubslice(slice, len); | ||
return { code: 'PUSHCONT', args: [subslice] }; | ||
let bits = slice.loadUint(4) * 8; | ||
let refs = 0; | ||
let bitsOffset = slice.offsetBits; | ||
let refsOffset = slice.offsetRefs; | ||
let cell = loadSubslice(slice, bits, refs); | ||
return { code: 'PUSHCONT', args: [cell, bitsOffset, refsOffset, bits, refs] }; | ||
}); | ||
@@ -540,5 +544,7 @@ CP0Auto.insertHex('a0', 8, { code: 'ADD' }); | ||
let refs = slice.loadUint(2); | ||
let dataBits = slice.loadUint(3) * 8 + 1; | ||
let subslice = fetchSubslice(slice, dataBits, refs); | ||
return { code: `STSLICECONST`, args: [subslice] }; | ||
let bits = slice.loadUint(3) * 8 + 1; | ||
let bitsOffset = slice.offsetBits; | ||
let refsOffset = slice.offsetRefs; | ||
let cell = loadSubslice(slice, bits, refs); | ||
return { code: `STSLICECONST`, args: [cell, bitsOffset, refsOffset, bits, refs] }; | ||
}); | ||
@@ -1168,1 +1174,12 @@ CP0Auto.insertHex('d0', 8, { code: 'CTOS' }); | ||
}); | ||
// | ||
// Utils | ||
// | ||
function loadSubslice(slice, bits, refs) { | ||
let b = (0, ton_core_1.beginCell)() | ||
.storeBits(slice.loadBits(bits)); | ||
for (let i = 0; i < (refs || 0); i++) { | ||
b.storeRef(slice.loadRef()); | ||
} | ||
return b.asCell(); | ||
} |
@@ -75,2 +75,8 @@ "use strict"; | ||
} | ||
// Slices | ||
if (op.code === 'PUSHSLICE' | ||
|| op.code === 'PUSHCONT' | ||
|| op.code === 'STSLICECONST') { | ||
return `${op.args[0]} ${op.code}`; | ||
} | ||
// Debug | ||
@@ -77,0 +83,0 @@ if (op.code === 'DEBUG') { |
@@ -12,7 +12,7 @@ "use strict"; | ||
const printer = args.printer; | ||
const hash = args.src.hash().toString('hex'); | ||
const writer = args.writer; | ||
const opcodes = (0, decompiler_1.decompile)({ | ||
src: args.src, | ||
srcOffset: args.srcOffset, | ||
offset: args.offset, | ||
limit: args.limit, | ||
allowUnknown: false | ||
@@ -33,3 +33,3 @@ }); | ||
let callRefs = new Map(); | ||
function extract(cell) { | ||
function extractCallRef(cell) { | ||
// Check if we have a call ref | ||
@@ -49,6 +49,7 @@ let k = cell.hash().toString('hex'); | ||
src: cell, | ||
srcOffset: 0, | ||
offset: { bits: 0, refs: 0 }, | ||
limit: null, | ||
root: false, | ||
writer: w, | ||
callRefExtractor: extract, | ||
callRefExtractor: extractCallRef, | ||
printer: args.printer | ||
@@ -68,6 +69,7 @@ }); | ||
src: value.cell, | ||
srcOffset: value.offset, | ||
offset: { bits: value.offset, refs: 0 }, | ||
limit: null, | ||
root: false, | ||
writer: w, | ||
callRefExtractor: extract, | ||
callRefExtractor: extractCallRef, | ||
printer: args.printer | ||
@@ -93,3 +95,3 @@ }); | ||
let opstr = `${key} ${value.ref ? 'PROCREF' : 'PROC'}:<{`; | ||
writer.append(printer({ op: opstr, offset: value.srcOffset, length: 0, hash, cell: value.src }, writer.indent)); | ||
writer.append(printer({ op: opstr, offset: value.srcOffset, length: 0, hash }, writer.indent)); | ||
writer.inIndent(() => { | ||
@@ -101,3 +103,3 @@ value.rendered.split('\n').forEach(line => { | ||
opstr = `}>`; | ||
writer.append(printer({ op: opstr, offset: value.srcOffset, length: 0, hash, cell: value.src }, writer.indent)); | ||
writer.append(printer({ op: opstr, offset: value.srcOffset, length: 0, hash }, writer.indent)); | ||
} | ||
@@ -110,23 +112,46 @@ }); | ||
for (const op of opcodes) { | ||
const opcode = op.op; | ||
// Special cases for call refs | ||
if (op.op.code === 'CALLREF' && args.callRefExtractor) { | ||
let id = args.callRefExtractor(op.op.args[0]); | ||
if (opcode.code === 'CALLREF' && args.callRefExtractor) { | ||
let id = args.callRefExtractor(opcode.args[0]); | ||
let opstr = `${id} INLINECALLDICT`; | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash, cell: args.src }, writer.indent)); | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash: op.hash }, writer.indent)); | ||
continue; | ||
} | ||
// Special case for PUSHCONT | ||
if (opcode.code === 'PUSHCONT') { | ||
let opstr = '<{'; | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash: op.hash }, writer.indent)); | ||
writer.inIndent(() => { | ||
decompileCell({ | ||
src: args.src, | ||
offset: { bits: opcode.args[1], refs: opcode.args[2] }, | ||
limit: { bits: opcode.args[3], refs: opcode.args[4] }, | ||
root: false, | ||
writer: writer, | ||
callRefExtractor: args.callRefExtractor, | ||
printer: args.printer | ||
}); | ||
}); | ||
opstr = '}> ' + op.op.code; | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash: op.hash }, writer.indent)); | ||
continue; | ||
} | ||
// Special cases for continuations | ||
if (op.op.code === 'PUSHCONT' | ||
|| op.op.code === 'IFREFELSE' | ||
|| op.op.code === 'CALLREF' | ||
|| op.op.code === 'IFJMPREF' | ||
|| op.op.code === 'IFREF' | ||
|| op.op.code === 'IFREFELSEREF') { | ||
let c = op.op.args[0]; | ||
if (opcode.code === 'IFREFELSE' | ||
|| opcode.code === 'CALLREF' | ||
|| opcode.code === 'IFJMPREF' | ||
|| opcode.code === 'IFREF' | ||
|| opcode.code === 'IFNOTREF' | ||
|| opcode.code === 'IFNOTJMPREF' | ||
|| opcode.code === 'IFREFELSEREF' | ||
|| opcode.code === 'IFELSEREF') { | ||
let c = opcode.args[0]; | ||
let opstr = '<{'; | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash, cell: args.src }, writer.indent)); | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash: op.hash }, writer.indent)); | ||
writer.inIndent(() => { | ||
decompileCell({ | ||
src: c, | ||
srcOffset: 0, | ||
offset: { bits: 0, refs: 0 }, | ||
limit: null, | ||
root: false, | ||
@@ -138,14 +163,14 @@ writer: writer, | ||
}); | ||
opstr = '}> ' + op.op.code; | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash, cell: args.src }, writer.indent)); | ||
opstr = '}> ' + opcode.code; | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash: op.hash }, writer.indent)); | ||
continue; | ||
} | ||
// Special cases for unknown opcode | ||
if (op.op.code === 'unknown') { | ||
writer.append('!' + op.op.data.toString()); | ||
if (opcode.code === 'unknown') { | ||
writer.append('!' + opcode.data.toString()); | ||
continue; | ||
} | ||
// All remaining opcodes | ||
let opstr = (0, opcodeToString_1.opcodeToString)(op.op); | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash, cell: args.src }, writer.indent)); | ||
let opstr = (0, opcodeToString_1.opcodeToString)(opcode); | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash: op.hash }, writer.indent)); | ||
} | ||
@@ -165,3 +190,4 @@ } | ||
src, | ||
srcOffset: 0, | ||
offset: { bits: 0, refs: 0 }, | ||
limit: null, | ||
root: true, | ||
@@ -180,14 +206,7 @@ writer, | ||
parse: (src) => { | ||
let bitsReader = src._reader.clone(); | ||
let offset = bitsReader._offset; | ||
bitsReader.reset(); | ||
let bits = bitsReader.loadBits(bitsReader.remaining); | ||
let b = (0, ton_core_1.beginCell)() | ||
.storeBits(bits); | ||
while (src.remainingRefs > 0) { | ||
b.storeRef(src.loadRef()); | ||
} | ||
return { offset, cell: b.endCell() }; | ||
let cloned = src.clone(true); | ||
let offset = src.offsetBits; | ||
return { offset, cell: cloned.asCell() }; | ||
} | ||
}; | ||
} |
/// <reference types="node" /> | ||
import { Cell, Slice } from "ton-core"; | ||
import { Cell } from "ton-core"; | ||
import { OpCode } from "../codepage/opcodes.gen"; | ||
@@ -11,2 +11,3 @@ import { Maybe } from "../utils/maybe"; | ||
op: DecompiledOpCode; | ||
hash: string; | ||
offset: number; | ||
@@ -16,5 +17,12 @@ length: number; | ||
export declare function decompile(args: { | ||
src: Cell | Slice | Buffer; | ||
srcOffset?: Maybe<number>; | ||
src: Cell | Buffer; | ||
offset?: Maybe<{ | ||
bits: number; | ||
refs: number; | ||
}>; | ||
limit?: Maybe<{ | ||
bits: number; | ||
refs: number; | ||
}>; | ||
allowUnknown?: boolean; | ||
}): DecompiledInstruction[]; |
@@ -6,2 +6,3 @@ "use strict"; | ||
const loadOpcode_1 = require("../codepage/loadOpcode"); | ||
const subcell_1 = require("../utils/subcell"); | ||
function decompile(args) { | ||
@@ -11,27 +12,39 @@ // Result collection | ||
// Load slice | ||
let sc; | ||
let source; | ||
if (Buffer.isBuffer(args.src)) { | ||
sc = ton_core_1.Cell.fromBoc(args.src)[0].beginParse(); | ||
source = ton_core_1.Cell.fromBoc(args.src)[0]; | ||
} | ||
else if (args.src instanceof ton_core_1.Cell) { | ||
sc = args.src.beginParse(); | ||
source = args.src; | ||
} | ||
else { | ||
sc = args.src; | ||
throw new Error('Invalid source'); | ||
} | ||
// Hash | ||
let hash = source.hash().toString('hex'); | ||
// Prepare offset | ||
let sco = args.srcOffset || 0; | ||
if (args.srcOffset && args.srcOffset > 0) { | ||
sc.skip(args.srcOffset); | ||
let bitsDelta = 0; | ||
let refsDelta = 0; | ||
if (args.offset) { | ||
bitsDelta = args.offset.bits; | ||
refsDelta = args.offset.refs; | ||
} | ||
// Prepare remaining tracker | ||
let scl = sc.remainingBits; | ||
while (sc.remainingBits > 0) { | ||
// Prepare offset | ||
let bitsLimit = args.limit ? (args.limit.bits + bitsDelta) : source.bits.length; | ||
let refsLimit = args.limit ? (args.limit.refs + refsDelta) : source.refs.length; | ||
let slice = (0, subcell_1.subcell)({ | ||
cell: source, | ||
bits: bitsLimit, | ||
refs: refsLimit | ||
}).beginParse(); | ||
if (args.offset) { | ||
slice.skip(args.offset.bits); | ||
for (let i = 0; i < args.offset.refs; i++) | ||
slice.loadRef(); | ||
} | ||
while (slice.remainingBits > 0) { | ||
// Load opcode | ||
const opcode = (0, loadOpcode_1.loadOpcode)(sc); | ||
// Update state | ||
let currentOffset = sco; // Persisted offset before reading opcode | ||
let currentLength = scl - sc.remainingBits; // Check difference in remaining bits to calculate opcode length | ||
scl -= currentLength; | ||
sco += currentLength; | ||
const opcodeOffset = slice.offsetBits; | ||
const opcode = (0, loadOpcode_1.loadOpcode)(slice); | ||
const opcodeLength = slice.offsetBits - opcodeOffset; | ||
// Failed case | ||
@@ -44,3 +57,3 @@ if (!opcode.ok) { | ||
} | ||
fullCell.storeSlice(sc); | ||
fullCell.storeSlice(slice); | ||
result.push({ | ||
@@ -51,4 +64,5 @@ op: { | ||
}, | ||
offset: currentOffset, | ||
length: currentLength | ||
hash, | ||
offset: opcodeOffset, | ||
length: opcodeLength | ||
}); | ||
@@ -64,10 +78,11 @@ break; | ||
op: opcode.read, | ||
offset: currentOffset, | ||
length: currentLength | ||
hash, | ||
offset: opcodeOffset, | ||
length: opcodeLength | ||
}); | ||
// Implicit jump | ||
if (sc.remainingBits === 0 && sc.remainingRefs > 0) { | ||
sc = sc.loadRef().beginParse(); | ||
scl = sc.remainingBits; | ||
sco = 0; | ||
if (slice.remainingBits === 0 && slice.remainingRefs > 0) { | ||
let n = slice.loadRef(); | ||
hash = n.hash().toString('hex'); | ||
slice = n.beginParse(); | ||
} | ||
@@ -74,0 +89,0 @@ } |
@@ -1,6 +0,4 @@ | ||
import { Cell } from "ton-core"; | ||
export type Printer = (src: string | { | ||
op: string; | ||
hash: string; | ||
cell: Cell; | ||
offset: number; | ||
@@ -7,0 +5,0 @@ length: number; |
{ | ||
"name": "@tact-lang/opcode", | ||
"version": "0.0.9", | ||
"version": "0.0.10", | ||
"main": "dist/index.js", | ||
@@ -23,3 +23,3 @@ "repository": "https://github.com/tact-lang/ton-opcode.git", | ||
"js-yaml": "^4.1.0", | ||
"ton-core": "^0.48.0", | ||
"ton-core": "^0.49.0", | ||
"ton-crypto": "^3.2.0", | ||
@@ -32,3 +32,3 @@ "ts-jest": "^29.0.5", | ||
"peerDependencies": { | ||
"ton-core": ">=0.48.0", | ||
"ton-core": ">=0.49.0", | ||
"ton-crypto": "^3.2.0" | ||
@@ -35,0 +35,0 @@ }, |
@@ -1,2 +0,2 @@ | ||
import { Slice } from "ton-core"; | ||
import { Cell, Slice } from "ton-core"; | ||
import { CP0Auto } from "./opcodes"; | ||
@@ -3,0 +3,0 @@ import { OpCode } from "./opcodes.gen"; |
@@ -41,4 +41,4 @@ import { Cell } from 'ton-core'; | ||
| { code: 'PUSHREFCONT', args: [Cell] } | ||
| { code: 'PUSHSLICE', args: [Cell] } | ||
| { code: 'PUSHCONT', args: [Cell] } | ||
| { code: 'PUSHSLICE', args: [Cell, number, number, number, number] } | ||
| { code: 'PUSHCONT', args: [Cell, number, number, number, number] } | ||
| { code: 'ADDCONST', args: [number] } | ||
@@ -65,3 +65,3 @@ | { code: 'MULCONST', args: [number] } | ||
| { code: 'BCHKBITSQ', args: [number] } | ||
| { code: 'STSLICECONST', args: [Cell] } | ||
| { code: 'STSLICECONST', args: [Cell, number, number, number, number] } | ||
| { code: 'LDI', args: [number] } | ||
@@ -68,0 +68,0 @@ | { code: 'LDU', args: [number] } |
import { beginCell, Slice } from 'ton-core'; | ||
import { Codepage } from './Codepage'; | ||
function fetchSubslice(slice: Slice, bits: number, refs?: number) { | ||
let b = beginCell() | ||
.storeBits(slice.loadBits(bits)); | ||
for (let i = 0; i < (refs || 0); i++) { | ||
b.storeRef(slice.loadRef()); | ||
} | ||
return b.asCell(); | ||
} | ||
const CP0Auto = new Codepage(); | ||
@@ -284,17 +275,24 @@ | ||
let x = slice.loadUint(4); | ||
let len = 8 * x + 4; | ||
let subslice = fetchSubslice(slice, len); | ||
return { code: 'PUSHSLICE', args: [subslice] }; | ||
let bits = 8 * x + 4; | ||
let refs = 0; | ||
let bitsOffset = slice.offsetBits; | ||
let refsOffset = slice.offsetRefs; | ||
let cell = loadSubslice(slice, bits, refs); | ||
return { code: 'PUSHSLICE', args: [cell, bitsOffset, refsOffset, bits, refs] }; | ||
}); | ||
CP0Auto.insertHex('8c', 8, (slice) => { | ||
let r = slice.loadUint(2) + 1; | ||
let xx = slice.loadUint(5); | ||
let subslice = fetchSubslice(slice, 8 * xx + 1, r); | ||
return { code: 'PUSHSLICE', args: [subslice] }; | ||
let refs = slice.loadUint(2) + 1; | ||
let bits = (8 * slice.loadUint(5) + 1); | ||
let bitsOffset = slice.offsetBits; | ||
let refsOffset = slice.offsetRefs; | ||
let cell = loadSubslice(slice, bits, refs); | ||
return { code: 'PUSHSLICE', args: [cell, bitsOffset, refsOffset, bits, refs] }; | ||
}); | ||
CP0Auto.insertHex('8d', 8, (slice) => { | ||
let r = slice.loadUint(3); | ||
let xx = slice.loadUint(7); | ||
let subslice = fetchSubslice(slice, 8 * xx + 6, r); | ||
return { code: 'PUSHSLICE', args: [subslice] }; | ||
let refs = slice.loadUint(3); | ||
let bits = 8 * slice.loadUint(7) + 6; | ||
let bitsOffset = slice.offsetBits; | ||
let refsOffset = slice.offsetRefs; | ||
let cell = loadSubslice(slice, bits, refs); | ||
return { code: 'PUSHSLICE', args: [cell, bitsOffset, refsOffset, bits, refs] }; | ||
}); | ||
@@ -305,11 +303,15 @@ // 9281536 (DUMMY) | ||
let refs = (args >> 7) & 3; | ||
let dataBytes = (args & 127) * 8; | ||
let subslice = fetchSubslice(slice, dataBytes, refs); | ||
return { code: 'PUSHCONT', args: [subslice] }; | ||
let bits = (args & 127) * 8; | ||
let bitsOffset = slice.offsetBits; | ||
let refsOffset = slice.offsetRefs; | ||
let cell = loadSubslice(slice, bits, refs); | ||
return { code: 'PUSHCONT', args: [cell, bitsOffset, refsOffset, bits, refs] }; | ||
}) | ||
CP0Auto.insertHex('9', 4, (slice) => { | ||
let len = slice.loadUint(4) * 8; | ||
let subslice = fetchSubslice(slice, len); | ||
return { code: 'PUSHCONT', args: [subslice] }; | ||
let bits = slice.loadUint(4) * 8; | ||
let refs = 0; | ||
let bitsOffset = slice.offsetBits; | ||
let refsOffset = slice.offsetRefs; | ||
let cell = loadSubslice(slice, bits, refs); | ||
return { code: 'PUSHCONT', args: [cell, bitsOffset, refsOffset, bits, refs] }; | ||
}) | ||
@@ -539,5 +541,7 @@ | ||
let refs = slice.loadUint(2); | ||
let dataBits = slice.loadUint(3) * 8 + 1; | ||
let subslice = fetchSubslice(slice, dataBits, refs); | ||
return { code: `STSLICECONST`, args: [subslice] }; | ||
let bits = slice.loadUint(3) * 8 + 1; | ||
let bitsOffset = slice.offsetBits; | ||
let refsOffset = slice.offsetRefs; | ||
let cell = loadSubslice(slice, bits, refs); | ||
return { code: `STSLICECONST`, args: [cell, bitsOffset, refsOffset, bits, refs] }; | ||
}); | ||
@@ -1177,2 +1181,15 @@ CP0Auto.insertHex('d0', 8, { code: 'CTOS' }); | ||
export { CP0Auto } | ||
export { CP0Auto } | ||
// | ||
// Utils | ||
// | ||
function loadSubslice(slice: Slice, bits: number, refs?: number) { | ||
let b = beginCell() | ||
.storeBits(slice.loadBits(bits)); | ||
for (let i = 0; i < (refs || 0); i++) { | ||
b.storeRef(slice.loadRef()); | ||
} | ||
return b.asCell(); | ||
} |
@@ -77,2 +77,9 @@ import { isOpCodeWithArgs, OpCode } from "./opcodes.gen"; | ||
// Slices | ||
if (op.code === 'PUSHSLICE' | ||
|| op.code === 'PUSHCONT' | ||
|| op.code === 'STSLICECONST') { | ||
return `${op.args[0]} ${op.code}`; | ||
} | ||
// Debug | ||
@@ -79,0 +86,0 @@ if (op.code === 'DEBUG') { |
@@ -1,2 +0,2 @@ | ||
import { beginCell, BitReader, Cell, Dictionary, DictionaryValue } from "ton-core"; | ||
import { Cell, Dictionary, DictionaryValue } from "ton-core"; | ||
import { opcodeToString } from "../codepage/opcodeToString"; | ||
@@ -11,3 +11,4 @@ import { Maybe } from "../utils/maybe"; | ||
src: Cell, | ||
srcOffset: number, | ||
offset: { bits: number, refs: number }, | ||
limit: { bits: number, refs: number } | null, | ||
root: boolean, | ||
@@ -19,7 +20,7 @@ writer: Writer, | ||
const printer = args.printer; | ||
const hash = args.src.hash().toString('hex'); | ||
const writer = args.writer; | ||
const opcodes = decompile({ | ||
src: args.src, | ||
srcOffset: args.srcOffset, | ||
offset: args.offset, | ||
limit: args.limit, | ||
allowUnknown: false | ||
@@ -43,3 +44,3 @@ }); | ||
let callRefs = new Map<string, string>(); | ||
function extract(cell: Cell) { | ||
function extractCallRef(cell: Cell) { | ||
@@ -62,6 +63,7 @@ // Check if we have a call ref | ||
src: cell, | ||
srcOffset: 0, | ||
offset: { bits: 0, refs: 0 }, | ||
limit: null, | ||
root: false, | ||
writer: w, | ||
callRefExtractor: extract, | ||
callRefExtractor: extractCallRef, | ||
printer: args.printer | ||
@@ -81,6 +83,7 @@ }); | ||
src: value.cell, | ||
srcOffset: value.offset, | ||
offset: { bits: value.offset, refs: 0 }, | ||
limit: null, | ||
root: false, | ||
writer: w, | ||
callRefExtractor: extract, | ||
callRefExtractor: extractCallRef, | ||
printer: args.printer | ||
@@ -107,3 +110,3 @@ }); | ||
let opstr = `${key} ${value.ref ? 'PROCREF' : 'PROC'}:<{`; | ||
writer.append(printer({ op: opstr, offset: value.srcOffset, length: 0, hash, cell: value.src }, writer.indent)); | ||
writer.append(printer({ op: opstr, offset: value.srcOffset, length: 0, hash }, writer.indent)); | ||
writer.inIndent(() => { | ||
@@ -115,3 +118,3 @@ value.rendered.split('\n').forEach(line => { | ||
opstr = `}>`; | ||
writer.append(printer({ op: opstr, offset: value.srcOffset, length: 0, hash, cell: value.src }, writer.indent)); | ||
writer.append(printer({ op: opstr, offset: value.srcOffset, length: 0, hash }, writer.indent)); | ||
} | ||
@@ -125,25 +128,49 @@ }); | ||
for (const op of opcodes) { | ||
const opcode = op.op; | ||
// Special cases for call refs | ||
if (op.op.code === 'CALLREF' && args.callRefExtractor) { | ||
let id = args.callRefExtractor(op.op.args[0]); | ||
if (opcode.code === 'CALLREF' && args.callRefExtractor) { | ||
let id = args.callRefExtractor(opcode.args[0]); | ||
let opstr = `${id} INLINECALLDICT`; | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash, cell: args.src }, writer.indent)); | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash: op.hash }, writer.indent)); | ||
continue; | ||
} | ||
// Special case for PUSHCONT | ||
if (opcode.code === 'PUSHCONT') { | ||
let opstr = '<{'; | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash: op.hash }, writer.indent)); | ||
writer.inIndent(() => { | ||
decompileCell({ | ||
src: args.src, | ||
offset: { bits: opcode.args[1], refs: opcode.args[2] }, | ||
limit: { bits: opcode.args[3], refs: opcode.args[4] }, | ||
root: false, | ||
writer: writer, | ||
callRefExtractor: args.callRefExtractor, | ||
printer: args.printer | ||
}); | ||
}) | ||
opstr = '}> ' + op.op.code; | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash: op.hash }, writer.indent)); | ||
continue; | ||
} | ||
// Special cases for continuations | ||
if (op.op.code === 'PUSHCONT' | ||
|| op.op.code === 'IFREFELSE' | ||
|| op.op.code === 'CALLREF' | ||
|| op.op.code === 'IFJMPREF' | ||
|| op.op.code === 'IFREF' | ||
|| op.op.code === 'IFREFELSEREF') { | ||
let c = op.op.args[0]; | ||
if (opcode.code === 'IFREFELSE' | ||
|| opcode.code === 'CALLREF' | ||
|| opcode.code === 'IFJMPREF' | ||
|| opcode.code === 'IFREF' | ||
|| opcode.code === 'IFNOTREF' | ||
|| opcode.code === 'IFNOTJMPREF' | ||
|| opcode.code === 'IFREFELSEREF' | ||
|| opcode.code === 'IFELSEREF') { | ||
let c = opcode.args[0]; | ||
let opstr = '<{'; | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash, cell: args.src }, writer.indent)); | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash: op.hash }, writer.indent)); | ||
writer.inIndent(() => { | ||
decompileCell({ | ||
src: c, | ||
srcOffset: 0, | ||
offset: { bits: 0, refs: 0 }, | ||
limit: null, | ||
root: false, | ||
@@ -155,4 +182,4 @@ writer: writer, | ||
}) | ||
opstr = '}> ' + op.op.code; | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash, cell: args.src }, writer.indent)); | ||
opstr = '}> ' + opcode.code; | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash: op.hash }, writer.indent)); | ||
continue; | ||
@@ -162,4 +189,4 @@ } | ||
// Special cases for unknown opcode | ||
if (op.op.code === 'unknown') { | ||
writer.append('!' + op.op.data.toString()); | ||
if (opcode.code === 'unknown') { | ||
writer.append('!' + opcode.data.toString()); | ||
continue; | ||
@@ -169,4 +196,4 @@ } | ||
// All remaining opcodes | ||
let opstr = opcodeToString(op.op); | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash, cell: args.src }, writer.indent)); | ||
let opstr = opcodeToString(opcode); | ||
writer.append(printer({ op: opstr, offset: op.offset, length: op.length, hash: op.hash }, writer.indent)); | ||
} | ||
@@ -186,3 +213,4 @@ } | ||
src, | ||
srcOffset: 0, | ||
offset: { bits: 0, refs: 0 }, | ||
limit: null, | ||
root: true, | ||
@@ -201,14 +229,7 @@ writer, | ||
parse: (src) => { | ||
let bitsReader = ((src as any)._reader.clone() as BitReader); | ||
let offset = (bitsReader as any)._offset as number; | ||
bitsReader.reset(); | ||
let bits = bitsReader.loadBits(bitsReader.remaining); | ||
let b = beginCell() | ||
.storeBits(bits); | ||
while (src.remainingRefs > 0) { | ||
b.storeRef(src.loadRef()); | ||
} | ||
return { offset, cell: b.endCell() }; | ||
let cloned = src.clone(true); | ||
let offset = src.offsetBits; | ||
return { offset, cell: cloned.asCell() }; | ||
} | ||
}; | ||
} |
@@ -5,2 +5,3 @@ import { beginCell, Cell, Slice } from "ton-core"; | ||
import { Maybe } from "../utils/maybe"; | ||
import { subcell } from "../utils/subcell"; | ||
@@ -10,2 +11,3 @@ export type DecompiledOpCode = OpCode | { code: 'unknown', data: Cell }; | ||
op: DecompiledOpCode, | ||
hash: string, | ||
offset: number, | ||
@@ -15,3 +17,8 @@ length: number | ||
export function decompile(args: { src: Cell | Slice | Buffer, srcOffset?: Maybe<number>, allowUnknown?: boolean }): DecompiledInstruction[] { | ||
export function decompile(args: { | ||
src: Cell | Buffer, | ||
offset?: Maybe<{ bits: number, refs: number }>, | ||
limit?: Maybe<{ bits: number, refs: number }>, | ||
allowUnknown?: boolean | ||
}): DecompiledInstruction[] { | ||
@@ -22,31 +29,43 @@ // Result collection | ||
// Load slice | ||
let sc: Slice; | ||
let source: Cell; | ||
if (Buffer.isBuffer(args.src)) { | ||
sc = Cell.fromBoc(args.src)[0].beginParse(); | ||
source = Cell.fromBoc(args.src)[0]; | ||
} else if (args.src instanceof Cell) { | ||
sc = args.src.beginParse(); | ||
source = args.src; | ||
} else { | ||
sc = args.src; | ||
throw new Error('Invalid source'); | ||
} | ||
// Hash | ||
let hash = source.hash().toString('hex'); | ||
// Prepare offset | ||
let sco = args.srcOffset || 0; | ||
if (args.srcOffset && args.srcOffset > 0) { | ||
sc.skip(args.srcOffset); | ||
let bitsDelta = 0; | ||
let refsDelta = 0; | ||
if (args.offset) { | ||
bitsDelta = args.offset.bits; | ||
refsDelta = args.offset.refs; | ||
} | ||
// Prepare remaining tracker | ||
let scl = sc.remainingBits; | ||
// Prepare offset | ||
let bitsLimit = args.limit ? (args.limit.bits + bitsDelta) : source.bits.length; | ||
let refsLimit = args.limit ? (args.limit.refs + refsDelta) : source.refs.length; | ||
let slice = subcell({ | ||
cell: source, | ||
bits: bitsLimit, | ||
refs: refsLimit | ||
}).beginParse(); | ||
if (args.offset) { | ||
slice.skip(args.offset.bits); | ||
for (let i = 0; i < args.offset.refs; i++) | ||
slice.loadRef(); | ||
} | ||
while (sc.remainingBits > 0) { | ||
while (slice.remainingBits > 0) { | ||
// Load opcode | ||
const opcode = loadOpcode(sc); | ||
const opcodeOffset = slice.offsetBits; | ||
const opcode = loadOpcode(slice); | ||
const opcodeLength = slice.offsetBits - opcodeOffset; | ||
// Update state | ||
let currentOffset = sco; // Persisted offset before reading opcode | ||
let currentLength = scl - sc.remainingBits; // Check difference in remaining bits to calculate opcode length | ||
scl -= currentLength; | ||
sco += currentLength; | ||
// Failed case | ||
@@ -59,3 +78,3 @@ if (!opcode.ok) { | ||
} | ||
fullCell.storeSlice(sc); | ||
fullCell.storeSlice(slice); | ||
result.push({ | ||
@@ -66,4 +85,5 @@ op: { | ||
}, | ||
offset: currentOffset, | ||
length: currentLength | ||
hash, | ||
offset: opcodeOffset, | ||
length: opcodeLength | ||
}); | ||
@@ -79,11 +99,12 @@ break; | ||
op: opcode.read, | ||
offset: currentOffset, | ||
length: currentLength | ||
hash, | ||
offset: opcodeOffset, | ||
length: opcodeLength | ||
}); | ||
// Implicit jump | ||
if (sc.remainingBits === 0 && sc.remainingRefs > 0) { | ||
sc = sc.loadRef().beginParse(); | ||
scl = sc.remainingBits; | ||
sco = 0; | ||
if (slice.remainingBits === 0 && slice.remainingRefs > 0) { | ||
let n = slice.loadRef(); | ||
hash = n.hash().toString('hex'); | ||
slice = n.beginParse(); | ||
} | ||
@@ -90,0 +111,0 @@ } |
@@ -1,5 +0,3 @@ | ||
import { Cell } from "ton-core"; | ||
export type Printer = (src: string | { op: string, hash: string, offset: number, length: number }, indent: number) => string; | ||
export type Printer = (src: string | { op: string, hash: string, cell: Cell, offset: number, length: number }, indent: number) => string; | ||
export function createTextPrinter(indentWidth: number): Printer { | ||
@@ -6,0 +4,0 @@ return (src, indent) => { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
6097457
66
6051