/* Copyright 2016 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { BinaryReader, BinaryReaderState, SectionCode, IExportEntry, IMemoryAddress, ExternalKind, ITypeEntry, IFunctionEntry, IFunctionInformation, IImportEntry, IOperatorInformation, Type, TypeKind, OperatorCode, OperatorCodeNames, Int64, ITableType, IMemoryType, ITagType, IGlobalType, IResizableLimits, IDataSegmentBody, IGlobalVariable, IElementSegment, IElementSegmentBody, ISectionInformation, IStartEntry, bytesToString, INameEntry, NameType, IFunctionNameEntry, ILocalNameEntry, ITagNameEntry, ITypeNameEntry, ITableNameEntry, IMemoryNameEntry, IGlobalNameEntry, IFieldNameEntry, ElementMode, RefType, CatchHandlerKind, } from "./WasmParser.js"; const NAME_SECTION_NAME = "name"; const INVALID_NAME_SYMBOLS_REGEX = /[^0-9A-Za-z!#$%&'*+.:<=>?@^_`|~\/\-]/; const INVALID_NAME_SYMBOLS_REGEX_GLOBAL = new RegExp( INVALID_NAME_SYMBOLS_REGEX.source, "g" ); function formatFloat32(n: number): string { if (n === 0) return 1 / n < 0 ? "-0.0" : "0.0"; if (isFinite(n)) return n.toString(); if (!isNaN(n)) return n < 0 ? "-inf" : "inf"; var view = new DataView(new ArrayBuffer(8)); view.setFloat32(0, n, true); var data = view.getInt32(0, true); var payload = data & 0x7fffff; const canonicalBits = 4194304; // 0x800..0 if (data > 0 && payload === canonicalBits) return "nan"; // canonical NaN; else if (payload === canonicalBits) return "-nan"; return (data < 0 ? "-" : "+") + "nan:0x" + payload.toString(16); } function formatFloat64(n: number): string { if (n === 0) return 1 / n < 0 ? "-0.0" : "0.0"; if (isFinite(n)) return n.toString(); if (!isNaN(n)) return n < 0 ? "-inf" : "inf"; var view = new DataView(new ArrayBuffer(8)); view.setFloat64(0, n, true); var data1 = view.getUint32(0, true); var data2 = view.getInt32(4, true); var payload = data1 + (data2 & 0xfffff) * 4294967296; const canonicalBits = 524288 * 4294967296; // 0x800..0 if (data2 > 0 && payload === canonicalBits) return "nan"; // canonical NaN; else if (payload === canonicalBits) return "-nan"; return (data2 < 0 ? "-" : "+") + "nan:0x" + payload.toString(16); } function formatI32Array(bytes, count) { var dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); var result = []; for (var i = 0; i < count; i++) result.push(`0x${formatHex(dv.getInt32(i << 2, true), 8)}`); return result.join(" "); } function formatI8Array(bytes, count) { var dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); var result = []; for (var i = 0; i < count; i++) result.push(`${dv.getInt8(i)}`); return result.join(" "); } function memoryAddressToString( address: IMemoryAddress, code: OperatorCode ): string { var defaultAlignFlags; switch (code) { case OperatorCode.v128_load: case OperatorCode.i16x8_load8x8_s: case OperatorCode.i16x8_load8x8_u: case OperatorCode.i32x4_load16x4_s: case OperatorCode.i32x4_load16x4_u: case OperatorCode.i64x2_load32x2_s: case OperatorCode.i64x2_load32x2_u: case OperatorCode.v8x16_load_splat: case OperatorCode.v16x8_load_splat: case OperatorCode.v32x4_load_splat: case OperatorCode.v64x2_load_splat: case OperatorCode.v128_store: defaultAlignFlags = 4; break; case OperatorCode.i64_load: case OperatorCode.i64_store: case OperatorCode.f64_load: case OperatorCode.f64_store: case OperatorCode.memory_atomic_wait64: case OperatorCode.i64_atomic_load: case OperatorCode.i64_atomic_store: case OperatorCode.i64_atomic_rmw_add: case OperatorCode.i64_atomic_rmw_sub: case OperatorCode.i64_atomic_rmw_and: case OperatorCode.i64_atomic_rmw_or: case OperatorCode.i64_atomic_rmw_xor: case OperatorCode.i64_atomic_rmw_xchg: case OperatorCode.i64_atomic_rmw_cmpxchg: case OperatorCode.v128_load64_zero: case OperatorCode.v128_load64_lane: case OperatorCode.v128_store64_lane: defaultAlignFlags = 3; break; case OperatorCode.i32_load: case OperatorCode.i64_load32_s: case OperatorCode.i64_load32_u: case OperatorCode.i32_store: case OperatorCode.i64_store32: case OperatorCode.f32_load: case OperatorCode.f32_store: case OperatorCode.memory_atomic_notify: case OperatorCode.memory_atomic_wait32: case OperatorCode.i32_atomic_load: case OperatorCode.i64_atomic_load32_u: case OperatorCode.i32_atomic_store: case OperatorCode.i64_atomic_store32: case OperatorCode.i32_atomic_rmw_add: case OperatorCode.i64_atomic_rmw32_add_u: case OperatorCode.i32_atomic_rmw_sub: case OperatorCode.i64_atomic_rmw32_sub_u: case OperatorCode.i32_atomic_rmw_and: case OperatorCode.i64_atomic_rmw32_and_u: case OperatorCode.i32_atomic_rmw_or: case OperatorCode.i64_atomic_rmw32_or_u: case OperatorCode.i32_atomic_rmw_xor: case OperatorCode.i64_atomic_rmw32_xor_u: case OperatorCode.i32_atomic_rmw_xchg: case OperatorCode.i64_atomic_rmw32_xchg_u: case OperatorCode.i32_atomic_rmw_cmpxchg: case OperatorCode.i64_atomic_rmw32_cmpxchg_u: case OperatorCode.v128_load32_zero: case OperatorCode.v128_load32_lane: case OperatorCode.v128_store32_lane: defaultAlignFlags = 2; break; case OperatorCode.i32_load16_s: case OperatorCode.i32_load16_u: case OperatorCode.i64_load16_s: case OperatorCode.i64_load16_u: case OperatorCode.i32_store16: case OperatorCode.i64_store16: case OperatorCode.i32_atomic_load16_u: case OperatorCode.i64_atomic_load16_u: case OperatorCode.i32_atomic_store16: case OperatorCode.i64_atomic_store16: case OperatorCode.i32_atomic_rmw16_add_u: case OperatorCode.i64_atomic_rmw16_add_u: case OperatorCode.i32_atomic_rmw16_sub_u: case OperatorCode.i64_atomic_rmw16_sub_u: case OperatorCode.i32_atomic_rmw16_and_u: case OperatorCode.i64_atomic_rmw16_and_u: case OperatorCode.i32_atomic_rmw16_or_u: case OperatorCode.i64_atomic_rmw16_or_u: case OperatorCode.i32_atomic_rmw16_xor_u: case OperatorCode.i64_atomic_rmw16_xor_u: case OperatorCode.i32_atomic_rmw16_xchg_u: case OperatorCode.i64_atomic_rmw16_xchg_u: case OperatorCode.i32_atomic_rmw16_cmpxchg_u: case OperatorCode.i64_atomic_rmw16_cmpxchg_u: case OperatorCode.v128_load16_lane: case OperatorCode.v128_store16_lane: defaultAlignFlags = 1; break; case OperatorCode.i32_load8_s: case OperatorCode.i32_load8_u: case OperatorCode.i64_load8_s: case OperatorCode.i64_load8_u: case OperatorCode.i32_store8: case OperatorCode.i64_store8: case OperatorCode.i32_atomic_load8_u: case OperatorCode.i64_atomic_load8_u: case OperatorCode.i32_atomic_store8: case OperatorCode.i64_atomic_store8: case OperatorCode.i32_atomic_rmw8_add_u: case OperatorCode.i64_atomic_rmw8_add_u: case OperatorCode.i32_atomic_rmw8_sub_u: case OperatorCode.i64_atomic_rmw8_sub_u: case OperatorCode.i32_atomic_rmw8_and_u: case OperatorCode.i64_atomic_rmw8_and_u: case OperatorCode.i32_atomic_rmw8_or_u: case OperatorCode.i64_atomic_rmw8_or_u: case OperatorCode.i32_atomic_rmw8_xor_u: case OperatorCode.i64_atomic_rmw8_xor_u: case OperatorCode.i32_atomic_rmw8_xchg_u: case OperatorCode.i64_atomic_rmw8_xchg_u: case OperatorCode.i32_atomic_rmw8_cmpxchg_u: case OperatorCode.i64_atomic_rmw8_cmpxchg_u: case OperatorCode.v128_load8_lane: case OperatorCode.v128_store8_lane: defaultAlignFlags = 0; break; } if (address.flags == defaultAlignFlags) // hide default flags return !address.offset ? null : `offset=${address.offset}`; if (!address.offset) // hide default offset return `align=${1 << address.flags}`; return `offset=${address.offset | 0} align=${1 << address.flags}`; } function limitsToString(limits: IResizableLimits): string { return ( limits.initial + (limits.maximum !== undefined ? " " + limits.maximum : "") ); } var paddingCache = ["0", "00", "000"]; function formatHex(n: number, width?: number): string { var s = (n >>> 0).toString(16).toUpperCase(); if (width === undefined || s.length >= width) return s; var paddingIndex = width - s.length - 1; while (paddingIndex >= paddingCache.length) paddingCache.push(paddingCache[paddingCache.length - 1] + "0"); return paddingCache[paddingIndex] + s; } const IndentIncrement = " "; function isValidName(name: string) { return !INVALID_NAME_SYMBOLS_REGEX.test(name); } export interface IExportMetadata { getFunctionExportNames(index: number): string[]; getGlobalExportNames(index: number): string[]; getMemoryExportNames(index: number): string[]; getTableExportNames(index: number): string[]; getTagExportNames(index: number): string[]; } export interface INameResolver { getTypeName(index: number, isRef: boolean): string; getTableName(index: number, isRef: boolean): string; getMemoryName(index: number, isRef: boolean): string; getGlobalName(index: number, isRef: boolean): string; getElementName(index: number, isRef: boolean): string; getTagName(index: number, isRef: boolean): string; getFunctionName(index: number, isImport: boolean, isRef: boolean): string; getVariableName(funcIndex: number, index: number, isRef: boolean): string; getFieldName(typeIndex: number, index: number, isRef: boolean): string; getLabel(index: number): string; } export class DefaultNameResolver implements INameResolver { public getTypeName(index: number, isRef: boolean): string { return "$type" + index; } public getTableName(index: number, isRef: boolean): string { return "$table" + index; } public getMemoryName(index: number, isRef: boolean): string { return "$memory" + index; } public getGlobalName(index: number, isRef: boolean): string { return "$global" + index; } public getElementName(index: number, isRef: boolean): string { return `$elem${index}`; } public getTagName(index: number, isRef: boolean): string { return `$tag${index}`; } public getFunctionName( index: number, isImport: boolean, isRef: boolean ): string { return (isImport ? "$import" : "$func") + index; } public getVariableName( funcIndex: number, index: number, isRef: boolean ): string { return "$var" + index; } public getFieldName( typeIndex: number, index: number, isRef: boolean ): string { return "$field" + index; } public getLabel(index: number): string { return "$label" + index; } } const EMPTY_STRING_ARRAY: string[] = []; class DevToolsExportMetadata implements IExportMetadata { private readonly _functionExportNames: string[][]; private readonly _globalExportNames: string[][]; private readonly _memoryExportNames: string[][]; private readonly _tableExportNames: string[][]; private readonly _eventExportNames: string[][]; constructor( functionExportNames: string[][], globalExportNames: string[][], memoryExportNames: string[][], tableExportNames: string[][], eventExportNames: string[][] ) { this._functionExportNames = functionExportNames; this._globalExportNames = globalExportNames; this._memoryExportNames = memoryExportNames; this._tableExportNames = tableExportNames; this._eventExportNames = eventExportNames; } public getFunctionExportNames(index: number) { return this._functionExportNames[index] ?? EMPTY_STRING_ARRAY; } public getGlobalExportNames(index: number) { return this._globalExportNames[index] ?? EMPTY_STRING_ARRAY; } public getMemoryExportNames(index: number) { return this._memoryExportNames[index] ?? EMPTY_STRING_ARRAY; } public getTableExportNames(index: number) { return this._tableExportNames[index] ?? EMPTY_STRING_ARRAY; } public getTagExportNames(index: number) { return this._eventExportNames[index] ?? EMPTY_STRING_ARRAY; } } export class NumericNameResolver implements INameResolver { public getTypeName(index: number, isRef: boolean): string { return isRef ? "" + index : `(;${index};)`; } public getTableName(index: number, isRef: boolean): string { return isRef ? "" + index : `(;${index};)`; } public getMemoryName(index: number, isRef: boolean): string { return isRef ? "" + index : `(;${index};)`; } public getGlobalName(index: number, isRef: boolean): string { return isRef ? "" + index : `(;${index};)`; } public getElementName(index: number, isRef: boolean): string { return isRef ? "" + index : `(;${index};)`; } public getTagName(index: number, isRef: boolean): string { return isRef ? "" + index : `(;${index};)`; } public getFunctionName( index: number, isImport: boolean, isRef: boolean ): string { return isRef ? "" + index : `(;${index};)`; } public getVariableName( funcIndex: number, index: number, isRef: boolean ): string { return isRef ? "" + index : `(;${index};)`; } public getFieldName( typeIndex: number, index: number, isRef: boolean ): string { return isRef ? "" : index + `(;${index};)`; } public getLabel(index: number): string { return null; } } export enum LabelMode { Depth, WhenUsed, Always, } // The breakable range is [start, end). export interface IFunctionBodyOffset { start: number; end: number; } export interface IDisassemblerResult { lines: Array; offsets?: Array; done: boolean; functionBodyOffsets?: Array; } export class WasmDisassembler { private _lines: Array; private _offsets: Array; private _buffer: string; private _types: Array; private _funcIndex: number; private _funcTypes: Array; private _importCount: number; private _globalCount: number; private _memoryCount: number; private _eventCount: number; private _tableCount: number; private _elementCount: number; private _expression: Array; private _backrefLabels: Array<{ line: number; position: number; useLabel: boolean; label: string; }>; private _labelIndex: number; private _indent: string; private _indentLevel: number; private _addOffsets: boolean; private _skipTypes = true; private _done: boolean; private _currentPosition: number; private _nameResolver: INameResolver; private _exportMetadata: IExportMetadata = null; private _labelMode: LabelMode; private _functionBodyOffsets: Array; private _currentFunctionBodyOffset: number; private _currentSectionId: SectionCode; private _logFirstInstruction: boolean; constructor() { this._lines = []; this._offsets = []; this._buffer = ""; this._indent = null; this._indentLevel = 0; this._addOffsets = false; this._done = false; this._currentPosition = 0; this._nameResolver = new DefaultNameResolver(); this._labelMode = LabelMode.WhenUsed; this._functionBodyOffsets = []; this._currentFunctionBodyOffset = 0; this._currentSectionId = SectionCode.Unknown; this._logFirstInstruction = false; this._reset(); } private _reset(): void { this._types = []; this._funcIndex = 0; this._funcTypes = []; this._importCount = 0; this._globalCount = 0; this._memoryCount = 0; this._eventCount = 0; this._tableCount = 0; this._elementCount = 0; this._expression = []; this._backrefLabels = null; this._labelIndex = 0; } public get addOffsets(): boolean { return this._addOffsets; } public set addOffsets(value: boolean) { if (this._currentPosition) throw new Error("Cannot switch addOffsets during processing."); this._addOffsets = value; } public get skipTypes(): boolean { return this._skipTypes; } public set skipTypes(skipTypes: boolean) { if (this._currentPosition) throw new Error("Cannot switch skipTypes during processing."); this._skipTypes = skipTypes; } public get labelMode(): LabelMode { return this._labelMode; } public set labelMode(value: LabelMode) { if (this._currentPosition) throw new Error("Cannot switch labelMode during processing."); this._labelMode = value; } public get exportMetadata(): IExportMetadata { return this._exportMetadata; } public set exportMetadata(exportMetadata: IExportMetadata) { if (this._currentPosition) throw new Error("Cannot switch exportMetadata during processing."); this._exportMetadata = exportMetadata; } public get nameResolver(): INameResolver { return this._nameResolver; } public set nameResolver(resolver: INameResolver) { if (this._currentPosition) throw new Error("Cannot switch nameResolver during processing."); this._nameResolver = resolver; } private appendBuffer(s: string) { this._buffer += s; } private newLine() { if (this.addOffsets) this._offsets.push(this._currentPosition); this._lines.push(this._buffer); this._buffer = ""; } private logStartOfFunctionBodyOffset() { if (this.addOffsets) { this._currentFunctionBodyOffset = this._currentPosition; } } private logEndOfFunctionBodyOffset() { if (this.addOffsets) { this._functionBodyOffsets.push({ start: this._currentFunctionBodyOffset, end: this._currentPosition, }); } } private typeIndexToString(typeIndex: number): string { if (typeIndex >= 0) return this._nameResolver.getTypeName(typeIndex, true); switch (typeIndex) { case TypeKind.funcref: return "func"; case TypeKind.externref: return "extern"; case TypeKind.anyref: return "any"; case TypeKind.eqref: return "eq"; case TypeKind.i31ref: return "i31"; case TypeKind.exnref: return "exnref"; case TypeKind.structref: return "struct"; case TypeKind.arrayref: return "array"; case TypeKind.nullfuncref: return "nofunc"; case TypeKind.nullexternref: return "noextern"; case TypeKind.nullref: return "none"; case TypeKind.nullexnref: return "noexnref"; } } private refTypeToString(typeIndex: number, nullable: boolean): string { return this.typeToString( new RefType(nullable ? TypeKind.ref_null : TypeKind.ref, typeIndex) ); } private typeToString(type: Type): string { switch (type.kind) { case TypeKind.i32: return "i32"; case TypeKind.i64: return "i64"; case TypeKind.f32: return "f32"; case TypeKind.f64: return "f64"; case TypeKind.v128: return "v128"; case TypeKind.i8: return "i8"; case TypeKind.i16: return "i16"; case TypeKind.funcref: return "funcref"; case TypeKind.externref: return "externref"; case TypeKind.exnref: return "exnref"; case TypeKind.anyref: return "anyref"; case TypeKind.eqref: return "eqref"; case TypeKind.i31ref: return "i31ref"; case TypeKind.structref: return "structref"; case TypeKind.arrayref: return "arrayref"; case TypeKind.nullfuncref: return "nullfuncref"; case TypeKind.nullexternref: return "nullexternref"; case TypeKind.nullexnref: return "nullexnref"; case TypeKind.nullref: return "nullref"; case TypeKind.ref: return `(ref ${this.typeIndexToString((type as RefType).ref_index)})`; case TypeKind.ref_null: return `(ref null ${this.typeIndexToString( (type as RefType).ref_index )})`; default: throw new Error(`Unexpected type ${JSON.stringify(type)}`); } } private maybeMut(type: string, mutability: boolean): string { return mutability ? `(mut ${type})` : type; } private globalTypeToString(type: IGlobalType): string { const typeStr = this.typeToString(type.contentType); return this.maybeMut(typeStr, !!type.mutability); } private printFuncType(typeIndex: number): void { var type = this._types[typeIndex]; if (type.params.length > 0) { this.appendBuffer(" (param"); for (var i = 0; i < type.params.length; i++) { this.appendBuffer(" "); this.appendBuffer(this.typeToString(type.params[i])); } this.appendBuffer(")"); } if (type.returns.length > 0) { this.appendBuffer(" (result"); for (var i = 0; i < type.returns.length; i++) { this.appendBuffer(" "); this.appendBuffer(this.typeToString(type.returns[i])); } this.appendBuffer(")"); } } private printStructType(typeIndex: number): void { var type = this._types[typeIndex]; if (type.fields.length === 0) return; for (var i = 0; i < type.fields.length; i++) { const fieldType = this.maybeMut( this.typeToString(type.fields[i]), type.mutabilities[i] ); const fieldName = this._nameResolver.getFieldName(typeIndex, i, false); this.appendBuffer(` (field ${fieldName} ${fieldType})`); } } private printArrayType(typeIndex: number): void { var type = this._types[typeIndex]; this.appendBuffer(" (field "); this.appendBuffer( this.maybeMut(this.typeToString(type.elementType), type.mutability) ); } private printBlockType(type: Type): void { if (type.kind === TypeKind.empty_block_type) { return; } if (type.kind === TypeKind.unspecified) { if (this._types[type.index].form == TypeKind.func) { return this.printFuncType(type.index); } else { // Encoding error. this.appendBuffer(` (type ${type.index})`); return; } } this.appendBuffer(" (result "); this.appendBuffer(this.typeToString(type)); this.appendBuffer(")"); } private printString(b: Uint8Array): void { this.appendBuffer('"'); for (var i = 0; i < b.length; i++) { var byte = b[i]; if ( byte < 0x20 || byte >= 0x7f || byte == /* " */ 0x22 || byte == /* \ */ 0x5c ) { this.appendBuffer( "\\" + (byte >> 4).toString(16) + (byte & 15).toString(16) ); } else { this.appendBuffer(String.fromCharCode(byte)); } } this.appendBuffer('"'); } private printExpression(expression: IOperatorInformation[]): void { for (const operator of expression) { this.appendBuffer("("); this.printOperator(operator); this.appendBuffer(")"); } } // extraDepthOffset is used by "delegate" instructions. private useLabel(depth: number, extraDepthOffset = 0): string { if (!this._backrefLabels) { return "" + depth; } var i = this._backrefLabels.length - depth - 1 - extraDepthOffset; if (i < 0) { return "" + depth; } var backrefLabel = this._backrefLabels[i]; if (!backrefLabel.useLabel) { backrefLabel.useLabel = true; backrefLabel.label = this._nameResolver.getLabel(this._labelIndex); var line = this._lines[backrefLabel.line]; this._lines[backrefLabel.line] = line.substring(0, backrefLabel.position) + " " + backrefLabel.label + line.substring(backrefLabel.position); this._labelIndex++; } return backrefLabel.label || "" + depth; } private printOperator(operator: IOperatorInformation): void { var code = operator.code; this.appendBuffer(OperatorCodeNames[code]); switch (code) { case OperatorCode.block: case OperatorCode.loop: case OperatorCode.if: case OperatorCode.try: case OperatorCode.try_table: if (this._labelMode !== LabelMode.Depth) { const backrefLabel = { line: this._lines.length, position: this._buffer.length, useLabel: false, label: null, }; if (this._labelMode === LabelMode.Always) { backrefLabel.useLabel = true; backrefLabel.label = this._nameResolver.getLabel( this._labelIndex++ ); if (backrefLabel.label) { this.appendBuffer(" "); this.appendBuffer(backrefLabel.label); } } this._backrefLabels.push(backrefLabel); } this.printBlockType(operator.blockType); if (operator.tryTable) { for (var i = 0; i < operator.tryTable.length; i++) { this.appendBuffer(" ("); switch (operator.tryTable[i].kind) { case CatchHandlerKind.Catch: this.appendBuffer("catch "); break; case CatchHandlerKind.CatchRef: this.appendBuffer("catch_ref "); break; case CatchHandlerKind.CatchAll: this.appendBuffer("catch_all "); break; case CatchHandlerKind.CatchAllRef: this.appendBuffer("catch_all_ref "); break; } if (operator.tryTable[i].tagIndex != null) { var tagName = this._nameResolver.getTagName( operator.tryTable[i].tagIndex, true ); this.appendBuffer(`${tagName} `); } this.appendBuffer(this.useLabel(operator.tryTable[i].depth + 1)); this.appendBuffer(")"); } } break; case OperatorCode.end: if (this._labelMode === LabelMode.Depth) { break; } const backrefLabel = this._backrefLabels.pop(); if (backrefLabel.label) { this.appendBuffer(" "); this.appendBuffer(backrefLabel.label); } break; case OperatorCode.br: case OperatorCode.br_if: case OperatorCode.br_on_null: case OperatorCode.br_on_non_null: this.appendBuffer(" "); this.appendBuffer(this.useLabel(operator.brDepth)); break; case OperatorCode.br_on_cast: case OperatorCode.br_on_cast_fail: this.appendBuffer(" flags=" + operator.literal); this.appendBuffer(" "); this.appendBuffer(this.typeIndexToString(operator.srcType)); this.appendBuffer(" "); this.appendBuffer(this.typeIndexToString(operator.refType)); this.appendBuffer(" "); this.appendBuffer(this.useLabel(operator.brDepth)); break; case OperatorCode.br_table: for (var i = 0; i < operator.brTable.length; i++) { this.appendBuffer(" "); this.appendBuffer(this.useLabel(operator.brTable[i])); } break; case OperatorCode.rethrow: this.appendBuffer(" "); this.appendBuffer(this.useLabel(operator.relativeDepth)); break; case OperatorCode.delegate: this.appendBuffer(" "); this.appendBuffer(this.useLabel(operator.relativeDepth, 1)); break; case OperatorCode.catch: case OperatorCode.throw: var tagName = this._nameResolver.getTagName(operator.tagIndex, true); this.appendBuffer(` ${tagName}`); break; case OperatorCode.ref_null: this.appendBuffer(" "); this.appendBuffer(this.typeIndexToString(operator.refType)); break; case OperatorCode.call: case OperatorCode.return_call: case OperatorCode.ref_func: var funcName = this._nameResolver.getFunctionName( operator.funcIndex, operator.funcIndex < this._importCount, true ); this.appendBuffer(` ${funcName}`); break; case OperatorCode.call_indirect: case OperatorCode.return_call_indirect: this.printFuncType(operator.typeIndex); break; case OperatorCode.select_with_type: { const selectType = this.typeToString(operator.selectType); this.appendBuffer(` ${selectType}`); break; } case OperatorCode.local_get: case OperatorCode.local_set: case OperatorCode.local_tee: var paramName = this._nameResolver.getVariableName( this._funcIndex, operator.localIndex, true ); this.appendBuffer(` ${paramName}`); break; case OperatorCode.global_get: case OperatorCode.global_set: var globalName = this._nameResolver.getGlobalName( operator.globalIndex, true ); this.appendBuffer(` ${globalName}`); break; case OperatorCode.i32_load: case OperatorCode.i64_load: case OperatorCode.f32_load: case OperatorCode.f64_load: case OperatorCode.i32_load8_s: case OperatorCode.i32_load8_u: case OperatorCode.i32_load16_s: case OperatorCode.i32_load16_u: case OperatorCode.i64_load8_s: case OperatorCode.i64_load8_u: case OperatorCode.i64_load16_s: case OperatorCode.i64_load16_u: case OperatorCode.i64_load32_s: case OperatorCode.i64_load32_u: case OperatorCode.i32_store: case OperatorCode.i64_store: case OperatorCode.f32_store: case OperatorCode.f64_store: case OperatorCode.i32_store8: case OperatorCode.i32_store16: case OperatorCode.i64_store8: case OperatorCode.i64_store16: case OperatorCode.i64_store32: case OperatorCode.memory_atomic_notify: case OperatorCode.memory_atomic_wait32: case OperatorCode.memory_atomic_wait64: case OperatorCode.i32_atomic_load: case OperatorCode.i64_atomic_load: case OperatorCode.i32_atomic_load8_u: case OperatorCode.i32_atomic_load16_u: case OperatorCode.i64_atomic_load8_u: case OperatorCode.i64_atomic_load16_u: case OperatorCode.i64_atomic_load32_u: case OperatorCode.i32_atomic_store: case OperatorCode.i64_atomic_store: case OperatorCode.i32_atomic_store8: case OperatorCode.i32_atomic_store16: case OperatorCode.i64_atomic_store8: case OperatorCode.i64_atomic_store16: case OperatorCode.i64_atomic_store32: case OperatorCode.i32_atomic_rmw_add: case OperatorCode.i64_atomic_rmw_add: case OperatorCode.i32_atomic_rmw8_add_u: case OperatorCode.i32_atomic_rmw16_add_u: case OperatorCode.i64_atomic_rmw8_add_u: case OperatorCode.i64_atomic_rmw16_add_u: case OperatorCode.i64_atomic_rmw32_add_u: case OperatorCode.i32_atomic_rmw_sub: case OperatorCode.i64_atomic_rmw_sub: case OperatorCode.i32_atomic_rmw8_sub_u: case OperatorCode.i32_atomic_rmw16_sub_u: case OperatorCode.i64_atomic_rmw8_sub_u: case OperatorCode.i64_atomic_rmw16_sub_u: case OperatorCode.i64_atomic_rmw32_sub_u: case OperatorCode.i32_atomic_rmw_and: case OperatorCode.i64_atomic_rmw_and: case OperatorCode.i32_atomic_rmw8_and_u: case OperatorCode.i32_atomic_rmw16_and_u: case OperatorCode.i64_atomic_rmw8_and_u: case OperatorCode.i64_atomic_rmw16_and_u: case OperatorCode.i64_atomic_rmw32_and_u: case OperatorCode.i32_atomic_rmw_or: case OperatorCode.i64_atomic_rmw_or: case OperatorCode.i32_atomic_rmw8_or_u: case OperatorCode.i32_atomic_rmw16_or_u: case OperatorCode.i64_atomic_rmw8_or_u: case OperatorCode.i64_atomic_rmw16_or_u: case OperatorCode.i64_atomic_rmw32_or_u: case OperatorCode.i32_atomic_rmw_xor: case OperatorCode.i64_atomic_rmw_xor: case OperatorCode.i32_atomic_rmw8_xor_u: case OperatorCode.i32_atomic_rmw16_xor_u: case OperatorCode.i64_atomic_rmw8_xor_u: case OperatorCode.i64_atomic_rmw16_xor_u: case OperatorCode.i64_atomic_rmw32_xor_u: case OperatorCode.i32_atomic_rmw_xchg: case OperatorCode.i64_atomic_rmw_xchg: case OperatorCode.i32_atomic_rmw8_xchg_u: case OperatorCode.i32_atomic_rmw16_xchg_u: case OperatorCode.i64_atomic_rmw8_xchg_u: case OperatorCode.i64_atomic_rmw16_xchg_u: case OperatorCode.i64_atomic_rmw32_xchg_u: case OperatorCode.i32_atomic_rmw_cmpxchg: case OperatorCode.i64_atomic_rmw_cmpxchg: case OperatorCode.i32_atomic_rmw8_cmpxchg_u: case OperatorCode.i32_atomic_rmw16_cmpxchg_u: case OperatorCode.i64_atomic_rmw8_cmpxchg_u: case OperatorCode.i64_atomic_rmw16_cmpxchg_u: case OperatorCode.i64_atomic_rmw32_cmpxchg_u: case OperatorCode.v128_load: case OperatorCode.i16x8_load8x8_s: case OperatorCode.i16x8_load8x8_u: case OperatorCode.i32x4_load16x4_s: case OperatorCode.i32x4_load16x4_u: case OperatorCode.i64x2_load32x2_s: case OperatorCode.i64x2_load32x2_u: case OperatorCode.v8x16_load_splat: case OperatorCode.v16x8_load_splat: case OperatorCode.v32x4_load_splat: case OperatorCode.v64x2_load_splat: case OperatorCode.v128_store: case OperatorCode.v128_load32_zero: case OperatorCode.v128_load64_zero: var memoryAddress = memoryAddressToString( operator.memoryAddress, operator.code ); if (memoryAddress !== null) { this.appendBuffer(" "); this.appendBuffer(memoryAddress); } break; case OperatorCode.memory_size: case OperatorCode.memory_grow: break; case OperatorCode.i32_const: this.appendBuffer(` ${(operator.literal).toString()}`); break; case OperatorCode.i64_const: this.appendBuffer(` ${(operator.literal).toString()}`); break; case OperatorCode.f32_const: this.appendBuffer(` ${formatFloat32(operator.literal)}`); break; case OperatorCode.f64_const: this.appendBuffer(` ${formatFloat64(operator.literal)}`); break; case OperatorCode.v128_const: this.appendBuffer(` i32x4 ${formatI32Array(operator.literal, 4)}`); break; case OperatorCode.i8x16_shuffle: this.appendBuffer(` ${formatI8Array(operator.lines, 16)}`); break; case OperatorCode.i8x16_extract_lane_s: case OperatorCode.i8x16_extract_lane_u: case OperatorCode.i8x16_replace_lane: case OperatorCode.i16x8_extract_lane_s: case OperatorCode.i16x8_extract_lane_u: case OperatorCode.i16x8_replace_lane: case OperatorCode.i32x4_extract_lane: case OperatorCode.i32x4_replace_lane: case OperatorCode.f32x4_extract_lane: case OperatorCode.f32x4_replace_lane: case OperatorCode.i64x2_extract_lane: case OperatorCode.i64x2_replace_lane: case OperatorCode.f64x2_extract_lane: case OperatorCode.f64x2_replace_lane: this.appendBuffer(` ${operator.lineIndex}`); break; case OperatorCode.v128_load8_lane: case OperatorCode.v128_load16_lane: case OperatorCode.v128_load32_lane: case OperatorCode.v128_load64_lane: case OperatorCode.v128_store8_lane: case OperatorCode.v128_store16_lane: case OperatorCode.v128_store32_lane: case OperatorCode.v128_store64_lane: var memoryAddress = memoryAddressToString( operator.memoryAddress, operator.code ); if (memoryAddress !== null) { this.appendBuffer(" "); this.appendBuffer(memoryAddress); } this.appendBuffer(` ${operator.lineIndex}`); break; case OperatorCode.memory_init: case OperatorCode.data_drop: this.appendBuffer(` ${operator.segmentIndex}`); break; case OperatorCode.elem_drop: const elementName = this._nameResolver.getElementName( operator.segmentIndex, true ); this.appendBuffer(` ${elementName}`); break; case OperatorCode.table_set: case OperatorCode.table_get: case OperatorCode.table_fill: { const tableName = this._nameResolver.getTableName( operator.tableIndex, true ); this.appendBuffer(` ${tableName}`); break; } case OperatorCode.table_copy: { // Table index might be omitted and defaults to 0. if (operator.tableIndex !== 0 || operator.destinationIndex !== 0) { const tableName = this._nameResolver.getTableName( operator.tableIndex, true ); const destinationName = this._nameResolver.getTableName( operator.destinationIndex, true ); this.appendBuffer(` ${destinationName} ${tableName}`); } break; } case OperatorCode.table_init: { // Table index might be omitted and defaults to 0. if (operator.tableIndex !== 0) { const tableName = this._nameResolver.getTableName( operator.tableIndex, true ); this.appendBuffer(` ${tableName}`); } const elementName = this._nameResolver.getElementName( operator.segmentIndex, true ); this.appendBuffer(` ${elementName}`); break; } case OperatorCode.struct_get: case OperatorCode.struct_get_s: case OperatorCode.struct_get_u: case OperatorCode.struct_set: { const refType = this.typeIndexToString(operator.refType); const fieldName = this._nameResolver.getFieldName( operator.refType, operator.fieldIndex, true ); this.appendBuffer(` ${refType} ${fieldName}`); break; } case OperatorCode.ref_cast: case OperatorCode.ref_test: { const refType = this.refTypeToString(operator.refType, false); this.appendBuffer(` ${refType}`); break; } case OperatorCode.ref_cast_null: case OperatorCode.ref_test_null: { const refType = this.refTypeToString(operator.refType, true); this.appendBuffer(` ${refType}`); break; } case OperatorCode.struct_new_default: case OperatorCode.struct_new: case OperatorCode.array_new_default: case OperatorCode.array_new: case OperatorCode.array_get: case OperatorCode.array_get_s: case OperatorCode.array_get_u: case OperatorCode.array_set: { const refType = this.typeIndexToString(operator.refType); this.appendBuffer(` ${refType}`); break; } case OperatorCode.array_fill: { const dstType = this.typeIndexToString(operator.refType); this.appendBuffer(` ${dstType}`); break; } case OperatorCode.array_copy: { const dstType = this.typeIndexToString(operator.refType); const srcType = this.typeIndexToString(operator.srcType); this.appendBuffer(` ${dstType} ${srcType}`); break; } case OperatorCode.array_new_fixed: { const refType = this.typeIndexToString(operator.refType); const length = operator.len; this.appendBuffer(` ${refType} ${length}`); break; } } } private printImportSource(info: IImportEntry): void { this.printString(info.module); this.appendBuffer(" "); this.printString(info.field); } private increaseIndent(): void { this._indent += IndentIncrement; this._indentLevel++; } private decreaseIndent(): void { this._indent = this._indent.slice(0, -IndentIncrement.length); this._indentLevel--; } public disassemble(reader: BinaryReader): string { const done = this.disassembleChunk(reader); if (!done) return null; let lines = this._lines; if (this._addOffsets) { lines = lines.map((line, index) => { var position = formatHex(this._offsets[index], 4); return line + " ;; @" + position; }); } lines.push(""); // we need '\n' after last line const result = lines.join("\n"); this._lines.length = 0; this._offsets.length = 0; this._functionBodyOffsets.length = 0; return result; } public getResult(): IDisassemblerResult { let linesReady = this._lines.length; if (this._backrefLabels && this._labelMode === LabelMode.WhenUsed) { this._backrefLabels.some((backrefLabel) => { if (backrefLabel.useLabel) return false; linesReady = backrefLabel.line; return true; }); } if (linesReady === 0) { return { lines: [], offsets: this._addOffsets ? [] : undefined, done: this._done, functionBodyOffsets: this._addOffsets ? [] : undefined, }; } if (linesReady === this._lines.length) { const result = { lines: this._lines, offsets: this._addOffsets ? this._offsets : undefined, done: this._done, functionBodyOffsets: this._addOffsets ? this._functionBodyOffsets : undefined, }; this._lines = []; if (this._addOffsets) { this._offsets = []; this._functionBodyOffsets = []; } return result; } const result = { lines: this._lines.splice(0, linesReady), offsets: this._addOffsets ? this._offsets.splice(0, linesReady) : undefined, done: false, functionBodyOffsets: this._addOffsets ? this._functionBodyOffsets : undefined, }; if (this._backrefLabels) { this._backrefLabels.forEach((backrefLabel) => { backrefLabel.line -= linesReady; }); } return result; } public disassembleChunk(reader: BinaryReader, offsetInModule = 0): boolean { if (this._done) throw new Error( "Invalid state: disassembly process was already finished." ); while (true) { this._currentPosition = reader.position + offsetInModule; if (!reader.read()) return false; switch (reader.state) { case BinaryReaderState.END_WASM: this.appendBuffer(")"); this.newLine(); this._reset(); if (!reader.hasMoreBytes()) { this._done = true; return true; } break; case BinaryReaderState.ERROR: throw reader.error; case BinaryReaderState.BEGIN_WASM: this.appendBuffer("(module"); this.newLine(); break; case BinaryReaderState.END_SECTION: this._currentSectionId = SectionCode.Unknown; break; case BinaryReaderState.BEGIN_SECTION: var sectionInfo = reader.result; switch (sectionInfo.id) { case SectionCode.Type: case SectionCode.Import: case SectionCode.Export: case SectionCode.Global: case SectionCode.Function: case SectionCode.Start: case SectionCode.Code: case SectionCode.Memory: case SectionCode.Data: case SectionCode.Table: case SectionCode.Element: case SectionCode.Tag: this._currentSectionId = sectionInfo.id; this._indent = " "; this._indentLevel = 0; break; // reading known section; default: reader.skipSection(); break; } break; case BinaryReaderState.MEMORY_SECTION_ENTRY: var memoryInfo = reader.result; var memoryIndex = this._memoryCount++; var memoryName = this._nameResolver.getMemoryName(memoryIndex, false); this.appendBuffer(` (memory ${memoryName}`); if (this._exportMetadata !== null) { for (const exportName of this._exportMetadata.getMemoryExportNames( memoryIndex )) { this.appendBuffer(` (export ${JSON.stringify(exportName)})`); } } this.appendBuffer(` ${limitsToString(memoryInfo.limits)}`); if (memoryInfo.shared) { this.appendBuffer(` shared`); } this.appendBuffer(")"); this.newLine(); break; case BinaryReaderState.TAG_SECTION_ENTRY: var tagInfo = reader.result; var tagIndex = this._eventCount++; var tagName = this._nameResolver.getTagName(tagIndex, false); this.appendBuffer(` (tag ${tagName}`); if (this._exportMetadata !== null) { for (const exportName of this._exportMetadata.getTagExportNames( tagIndex )) { this.appendBuffer(` (export ${JSON.stringify(exportName)})`); } } this.printFuncType(tagInfo.typeIndex); this.appendBuffer(")"); this.newLine(); break; case BinaryReaderState.TABLE_SECTION_ENTRY: var tableInfo = reader.result; var tableIndex = this._tableCount++; var tableName = this._nameResolver.getTableName(tableIndex, false); this.appendBuffer(` (table ${tableName}`); if (this._exportMetadata !== null) { for (const exportName of this._exportMetadata.getTableExportNames( tableIndex )) { this.appendBuffer(` (export ${JSON.stringify(exportName)})`); } } this.appendBuffer( ` ${limitsToString(tableInfo.limits)} ${this.typeToString( tableInfo.elementType )})` ); this.newLine(); break; case BinaryReaderState.EXPORT_SECTION_ENTRY: // Skip printing exports here when we have export metadata // which we can use to print export information inline. if (this._exportMetadata === null) { var exportInfo = reader.result; this.appendBuffer(" (export "); this.printString(exportInfo.field); this.appendBuffer(" "); switch (exportInfo.kind) { case ExternalKind.Function: var funcName = this._nameResolver.getFunctionName( exportInfo.index, exportInfo.index < this._importCount, true ); this.appendBuffer(`(func ${funcName})`); break; case ExternalKind.Table: var tableName = this._nameResolver.getTableName( exportInfo.index, true ); this.appendBuffer(`(table ${tableName})`); break; case ExternalKind.Memory: var memoryName = this._nameResolver.getMemoryName( exportInfo.index, true ); this.appendBuffer(`(memory ${memoryName})`); break; case ExternalKind.Global: var globalName = this._nameResolver.getGlobalName( exportInfo.index, true ); this.appendBuffer(`(global ${globalName})`); break; case ExternalKind.Tag: var tagName = this._nameResolver.getTagName( exportInfo.index, true ); this.appendBuffer(`(tag ${tagName})`); break; default: throw new Error(`Unsupported export ${exportInfo.kind}`); } this.appendBuffer(")"); this.newLine(); } break; case BinaryReaderState.IMPORT_SECTION_ENTRY: var importInfo = reader.result; switch (importInfo.kind) { case ExternalKind.Function: this._importCount++; var funcIndex = this._funcIndex++; var funcName = this._nameResolver.getFunctionName( funcIndex, true, false ); this.appendBuffer(` (func ${funcName}`); if (this._exportMetadata !== null) { for (const exportName of this._exportMetadata.getFunctionExportNames( funcIndex )) { this.appendBuffer(` (export ${JSON.stringify(exportName)})`); } } this.appendBuffer(` (import `); this.printImportSource(importInfo); this.appendBuffer(")"); this.printFuncType(importInfo.funcTypeIndex); this.appendBuffer(")"); break; case ExternalKind.Global: var globalImportInfo = importInfo.type; var globalIndex = this._globalCount++; var globalName = this._nameResolver.getGlobalName( globalIndex, false ); this.appendBuffer(` (global ${globalName}`); if (this._exportMetadata !== null) { for (const exportName of this._exportMetadata.getGlobalExportNames( globalIndex )) { this.appendBuffer(` (export ${JSON.stringify(exportName)})`); } } this.appendBuffer(` (import `); this.printImportSource(importInfo); this.appendBuffer( `) ${this.globalTypeToString(globalImportInfo)})` ); break; case ExternalKind.Memory: var memoryImportInfo = importInfo.type; var memoryIndex = this._memoryCount++; var memoryName = this._nameResolver.getMemoryName( memoryIndex, false ); this.appendBuffer(` (memory ${memoryName}`); if (this._exportMetadata !== null) { for (const exportName of this._exportMetadata.getMemoryExportNames( memoryIndex )) { this.appendBuffer(` (export ${JSON.stringify(exportName)})`); } } this.appendBuffer(` (import `); this.printImportSource(importInfo); this.appendBuffer(`) ${limitsToString(memoryImportInfo.limits)}`); if (memoryImportInfo.shared) { this.appendBuffer(` shared`); } this.appendBuffer(")"); break; case ExternalKind.Table: var tableImportInfo = importInfo.type; var tableIndex = this._tableCount++; var tableName = this._nameResolver.getTableName( tableIndex, false ); this.appendBuffer(` (table ${tableName}`); if (this._exportMetadata !== null) { for (const exportName of this._exportMetadata.getTableExportNames( tableIndex )) { this.appendBuffer(` (export ${JSON.stringify(exportName)})`); } } this.appendBuffer(` (import `); this.printImportSource(importInfo); this.appendBuffer( `) ${limitsToString( tableImportInfo.limits )} ${this.typeToString(tableImportInfo.elementType)})` ); break; case ExternalKind.Tag: var eventImportInfo = importInfo.type; var tagIndex = this._eventCount++; var tagName = this._nameResolver.getTagName(tagIndex, false); this.appendBuffer(` (tag ${tagName}`); if (this._exportMetadata !== null) { for (const exportName of this._exportMetadata.getTagExportNames( tagIndex )) { this.appendBuffer(` (export ${JSON.stringify(exportName)})`); } } this.appendBuffer(` (import `); this.printImportSource(importInfo); this.appendBuffer(")"); this.printFuncType(eventImportInfo.typeIndex); this.appendBuffer(")"); break; default: throw new Error(`NYI other import types: ${importInfo.kind}`); } this.newLine(); break; case BinaryReaderState.BEGIN_ELEMENT_SECTION_ENTRY: var elementSegment = reader.result; var elementIndex = this._elementCount++; var elementName = this._nameResolver.getElementName( elementIndex, false ); this.appendBuffer(` (elem ${elementName}`); switch (elementSegment.mode) { case ElementMode.Active: if (elementSegment.tableIndex !== 0) { const tableName = this._nameResolver.getTableName( elementSegment.tableIndex, false ); this.appendBuffer(` (table ${tableName})`); } break; case ElementMode.Passive: break; case ElementMode.Declarative: this.appendBuffer(" declare"); break; } break; case BinaryReaderState.END_ELEMENT_SECTION_ENTRY: this.appendBuffer(")"); this.newLine(); break; case BinaryReaderState.ELEMENT_SECTION_ENTRY_BODY: const elementSegmentBody = reader.result; this.appendBuffer( ` ${this.typeToString(elementSegmentBody.elementType)}` ); break; case BinaryReaderState.BEGIN_GLOBAL_SECTION_ENTRY: var globalInfo = reader.result; var globalIndex = this._globalCount++; var globalName = this._nameResolver.getGlobalName(globalIndex, false); this.appendBuffer(` (global ${globalName}`); if (this._exportMetadata !== null) { for (const exportName of this._exportMetadata.getGlobalExportNames( globalIndex )) { this.appendBuffer(` (export ${JSON.stringify(exportName)})`); } } this.appendBuffer(` ${this.globalTypeToString(globalInfo.type)}`); break; case BinaryReaderState.END_GLOBAL_SECTION_ENTRY: this.appendBuffer(")"); this.newLine(); break; case BinaryReaderState.TYPE_SECTION_ENTRY: var typeEntry = reader.result; var typeIndex = this._types.length; this._types.push(typeEntry); if (!this._skipTypes) { var typeName = this._nameResolver.getTypeName(typeIndex, false); var superTypeName = undefined; if (typeEntry.supertypes !== undefined) { superTypeName = typeEntry.supertypes .map((ty) => this.typeIndexToString(ty)) .join("+"); } this.appendBuffer(this._indent); this.appendBuffer(`(type ${typeName} `); var subtype = typeEntry.supertypes || typeEntry.final; if (subtype) { this.appendBuffer("(sub "); if (typeEntry.final) this.appendBuffer("final "); if (typeEntry.supertypes) { this.appendBuffer( typeEntry.supertypes .map((ty) => this.typeIndexToString(ty)) .join(" ") ); this.appendBuffer(" "); } } if (typeEntry.form === TypeKind.func) { this.appendBuffer(`(func`); this.printFuncType(typeIndex); this.appendBuffer(")"); } else if (typeEntry.form === TypeKind.struct) { this.appendBuffer(`(struct`); this.printStructType(typeIndex); this.appendBuffer(")"); } else if (typeEntry.form === TypeKind.array) { this.appendBuffer(`(array`); this.printArrayType(typeIndex); this.appendBuffer(")"); } else { throw new Error(`Unknown type form: ${typeEntry.form}`); } if (subtype) { this.appendBuffer(")"); } this.appendBuffer(")"); this.newLine(); } break; case BinaryReaderState.START_SECTION_ENTRY: var startEntry = reader.result; var funcName = this._nameResolver.getFunctionName( startEntry.index, startEntry.index < this._importCount, true ); this.appendBuffer(` (start ${funcName})`); this.newLine(); break; case BinaryReaderState.BEGIN_DATA_SECTION_ENTRY: this.appendBuffer(" (data"); break; case BinaryReaderState.DATA_SECTION_ENTRY_BODY: var body = reader.result; this.appendBuffer(" "); this.printString(body.data); break; case BinaryReaderState.END_DATA_SECTION_ENTRY: this.appendBuffer(")"); this.newLine(); break; case BinaryReaderState.BEGIN_INIT_EXPRESSION_BODY: case BinaryReaderState.BEGIN_OFFSET_EXPRESSION_BODY: this._expression = []; break; case BinaryReaderState.INIT_EXPRESSION_OPERATOR: case BinaryReaderState.OFFSET_EXPRESSION_OPERATOR: var operator = reader.result; if (operator.code !== OperatorCode.end) { this._expression.push(operator); } break; case BinaryReaderState.END_OFFSET_EXPRESSION_BODY: if (this._expression.length > 1) { this.appendBuffer(" (offset "); this.printExpression(this._expression); this.appendBuffer(")"); } else { this.appendBuffer(" "); this.printExpression(this._expression); } this._expression = []; break; case BinaryReaderState.END_INIT_EXPRESSION_BODY: if ( this._expression.length > 1 && this._currentSectionId === SectionCode.Element ) { this.appendBuffer(" (item "); this.printExpression(this._expression); this.appendBuffer(")"); } else { this.appendBuffer(" "); this.printExpression(this._expression); } this._expression = []; break; case BinaryReaderState.FUNCTION_SECTION_ENTRY: this._funcTypes.push((reader.result).typeIndex); break; case BinaryReaderState.BEGIN_FUNCTION_BODY: var func = reader.result; var type = this._types[this._funcTypes[this._funcIndex - this._importCount]]; this.appendBuffer(" (func "); this.appendBuffer( this._nameResolver.getFunctionName(this._funcIndex, false, false) ); if (this._exportMetadata !== null) { for (const exportName of this._exportMetadata.getFunctionExportNames( this._funcIndex )) { this.appendBuffer(` (export ${JSON.stringify(exportName)})`); } } for (var i = 0; i < type.params.length; i++) { var paramName = this._nameResolver.getVariableName( this._funcIndex, i, false ); this.appendBuffer( ` (param ${paramName} ${this.typeToString(type.params[i])})` ); } for (var i = 0; i < type.returns.length; i++) { this.appendBuffer( ` (result ${this.typeToString(type.returns[i])})` ); } this.newLine(); var localIndex = type.params.length; if (func.locals.length > 0) { this.appendBuffer(" "); for (var l of func.locals) { for (var i = 0; i < l.count; i++) { var paramName = this._nameResolver.getVariableName( this._funcIndex, localIndex++, false ); this.appendBuffer( ` (local ${paramName} ${this.typeToString(l.type)})` ); } } this.newLine(); } this._indent = " "; this._indentLevel = 0; this._labelIndex = 0; this._backrefLabels = this._labelMode === LabelMode.Depth ? null : []; this._logFirstInstruction = true; break; case BinaryReaderState.CODE_OPERATOR: if (this._logFirstInstruction) { this.logStartOfFunctionBodyOffset(); this._logFirstInstruction = false; } var operator = reader.result; if (operator.code == OperatorCode.end && this._indentLevel == 0) { // reached of the function, closing function body this.appendBuffer(` )`); this.newLine(); break; } switch (operator.code) { case OperatorCode.end: case OperatorCode.else: case OperatorCode.catch: case OperatorCode.catch_all: case OperatorCode.delegate: this.decreaseIndent(); break; } this.appendBuffer(this._indent); this.printOperator(operator); this.newLine(); switch (operator.code) { case OperatorCode.if: case OperatorCode.block: case OperatorCode.loop: case OperatorCode.else: case OperatorCode.try: case OperatorCode.try_table: case OperatorCode.catch: case OperatorCode.catch_all: this.increaseIndent(); break; } break; case BinaryReaderState.END_FUNCTION_BODY: this._funcIndex++; this._backrefLabels = null; this.logEndOfFunctionBodyOffset(); // See case BinaryReaderState.CODE_OPERATOR for closing of body break; case BinaryReaderState.BEGIN_REC_GROUP: if (!this._skipTypes) { this.appendBuffer(` (rec`); this.newLine(); this.increaseIndent(); } break; case BinaryReaderState.END_REC_GROUP: if (!this._skipTypes) { this.decreaseIndent(); this.appendBuffer(` )`); this.newLine(); } break; default: throw new Error(`Expectected state: ${reader.state}`); } } } } const UNKNOWN_FUNCTION_PREFIX = "unknown"; class NameSectionNameResolver extends DefaultNameResolver { protected readonly _functionNames: string[]; protected readonly _localNames: string[][]; protected readonly _tagNames: string[]; protected readonly _typeNames: string[]; protected readonly _tableNames: string[]; protected readonly _memoryNames: string[]; protected readonly _globalNames: string[]; protected readonly _fieldNames: string[][]; constructor( functionNames: string[], localNames: string[][], tagNames: string[], typeNames: string[], tableNames: string[], memoryNames: string[], globalNames: string[], fieldNames: string[][] ) { super(); this._functionNames = functionNames; this._localNames = localNames; this._tagNames = tagNames; this._typeNames = typeNames; this._tableNames = tableNames; this._memoryNames = memoryNames; this._globalNames = globalNames; this._fieldNames = fieldNames; } public getTypeName(index: number, isRef: boolean): string { const name = this._typeNames[index]; if (!name) return super.getTypeName(index, isRef); return isRef ? `$${name}` : `$${name} (;${index};)`; } public getTableName(index: number, isRef: boolean): string { const name = this._tableNames[index]; if (!name) return super.getTableName(index, isRef); return isRef ? `$${name}` : `$${name} (;${index};)`; } public getMemoryName(index: number, isRef: boolean): string { const name = this._memoryNames[index]; if (!name) return super.getMemoryName(index, isRef); return isRef ? `$${name}` : `$${name} (;${index};)`; } public getGlobalName(index: number, isRef: boolean): string { const name = this._globalNames[index]; if (!name) return super.getGlobalName(index, isRef); return isRef ? `$${name}` : `$${name} (;${index};)`; } public getTagName(index: number, isRef: boolean): string { const name = this._tagNames[index]; if (!name) return super.getTagName(index, isRef); return isRef ? `$${name}` : `$${name} (;${index};)`; } public getFunctionName( index: number, isImport: boolean, isRef: boolean ): string { const name = this._functionNames[index]; if (!name) return `$${UNKNOWN_FUNCTION_PREFIX}${index}`; return isRef ? `$${name}` : `$${name} (;${index};)`; } public getVariableName( funcIndex: number, index: number, isRef: boolean ): string { const name = this._localNames[funcIndex] && this._localNames[funcIndex][index]; if (!name) return super.getVariableName(funcIndex, index, isRef); return isRef ? `$${name}` : `$${name} (;${index};)`; } public getFieldName( typeIndex: number, index: number, isRef: boolean ): string { const name = this._fieldNames[typeIndex] && this._fieldNames[typeIndex][index]; if (!name) return super.getFieldName(typeIndex, index, isRef); return isRef ? `$${name}` : `$${name} (;${index};)`; } } export class NameSectionReader { private _done = false; private _functionsCount = 0; private _functionImportsCount = 0; private _functionNames: string[] = null; private _functionLocalNames: string[][] = null; private _tagNames: string[] = null; private _typeNames: string[] = null; private _tableNames: string[] = null; private _memoryNames: string[] = null; private _globalNames: string[] = null; private _fieldNames: string[][] = null; private _hasNames = false; public read(reader: BinaryReader): boolean { if (this._done) throw new Error( "Invalid state: disassembly process was already finished." ); while (true) { if (!reader.read()) return false; switch (reader.state) { case BinaryReaderState.END_WASM: if (!reader.hasMoreBytes()) { this._done = true; return true; } break; case BinaryReaderState.ERROR: throw reader.error; case BinaryReaderState.BEGIN_WASM: this._functionsCount = 0; this._functionImportsCount = 0; this._functionNames = []; this._functionLocalNames = []; this._tagNames = []; this._typeNames = []; this._tableNames = []; this._memoryNames = []; this._globalNames = []; this._fieldNames = []; this._hasNames = false; break; case BinaryReaderState.END_SECTION: break; case BinaryReaderState.BEGIN_SECTION: var sectionInfo = reader.result; if ( sectionInfo.id === SectionCode.Custom && bytesToString(sectionInfo.name) === NAME_SECTION_NAME ) { break; } if ( sectionInfo.id === SectionCode.Function || sectionInfo.id === SectionCode.Import ) { break; } reader.skipSection(); break; case BinaryReaderState.IMPORT_SECTION_ENTRY: var importInfo = reader.result; if (importInfo.kind === ExternalKind.Function) this._functionImportsCount++; break; case BinaryReaderState.FUNCTION_SECTION_ENTRY: this._functionsCount++; break; case BinaryReaderState.NAME_SECTION_ENTRY: const nameInfo = reader.result; if (nameInfo.type === NameType.Function) { const { names } = nameInfo; names.forEach(({ index, name }) => { this._functionNames[index] = bytesToString(name); }); this._hasNames = true; } else if (nameInfo.type === NameType.Local) { const { funcs } = nameInfo; funcs.forEach(({ index, locals }) => { const localNames = (this._functionLocalNames[index] = []); locals.forEach(({ index, name }) => { localNames[index] = bytesToString(name); }); }); this._hasNames = true; } else if (nameInfo.type === NameType.Tag) { const { names } = nameInfo; names.forEach(({ index, name }) => { this._tagNames[index] = bytesToString(name); }); this._hasNames = true; } else if (nameInfo.type === NameType.Type) { const { names } = nameInfo; names.forEach(({ index, name }) => { this._typeNames[index] = bytesToString(name); }); this._hasNames = true; } else if (nameInfo.type === NameType.Table) { const { names } = nameInfo; names.forEach(({ index, name }) => { this._tableNames[index] = bytesToString(name); }); this._hasNames = true; } else if (nameInfo.type === NameType.Memory) { const { names } = nameInfo; names.forEach(({ index, name }) => { this._memoryNames[index] = bytesToString(name); }); this._hasNames = true; } else if (nameInfo.type === NameType.Global) { const { names } = nameInfo; names.forEach(({ index, name }) => { this._globalNames[index] = bytesToString(name); }); this._hasNames = true; } else if (nameInfo.type === NameType.Field) { const { types } = nameInfo; types.forEach(({ index, fields }) => { const fieldNames = (this._fieldNames[index] = []); fields.forEach(({ index, name }) => { fieldNames[index] = bytesToString(name); }); }); } break; default: throw new Error(`Expectected state: ${reader.state}`); } } } public hasValidNames(): boolean { return this._hasNames; } public getNameResolver(): INameResolver { if (!this.hasValidNames()) throw new Error("Has no valid name section"); // Fix bad names. const functionNamesLength = this._functionImportsCount + this._functionsCount; const functionNames = this._functionNames.slice(0, functionNamesLength); const usedNameAt = Object.create(null); for (let i = 0; i < functionNames.length; i++) { const name = functionNames[i]; if (!name) continue; const goodName = !(name in usedNameAt) && isValidName(name) && name.indexOf(UNKNOWN_FUNCTION_PREFIX) !== 0; if (!goodName) { if (usedNameAt[name] >= 0) { // Remove all non-unique names. functionNames[usedNameAt[name]] = null; usedNameAt[name] = -1; } functionNames[i] = null; continue; } usedNameAt[name] = i; } return new NameSectionNameResolver( functionNames, this._functionLocalNames, this._tagNames, this._typeNames, this._tableNames, this._memoryNames, this._globalNames, this._fieldNames ); } } export class DevToolsNameResolver extends NameSectionNameResolver { constructor( functionNames: string[], localNames: string[][], tagNames: string[], typeNames: string[], tableNames: string[], memoryNames: string[], globalNames: string[], fieldNames: string[][] ) { super( functionNames, localNames, tagNames, typeNames, tableNames, memoryNames, globalNames, fieldNames ); } public getFunctionName( index: number, isImport: boolean, isRef: boolean ): string { const name = this._functionNames[index]; if (!name) return isImport ? `$import${index}` : `$func${index}`; return isRef ? `$${name}` : `$${name} (;${index};)`; } } export class DevToolsNameGenerator { private _done = false; private _functionImportsCount = 0; private _memoryImportsCount = 0; private _tableImportsCount = 0; private _globalImportsCount = 0; private _tagImportsCount = 0; private _functionNames: string[] = null; private _functionLocalNames: string[][] = null; private _tagNames: string[] = null; private _memoryNames: string[] = null; private _typeNames: string[] = null; private _tableNames: string[] = null; private _globalNames: string[] = null; private _fieldNames: string[][] = null; private _functionExportNames: string[][] = null; private _globalExportNames: string[][] = null; private _memoryExportNames: string[][] = null; private _tableExportNames: string[][] = null; private _tagExportNames: string[][] = null; private _addExportName(exportNames: string[][], index: number, name: string) { const names = exportNames[index]; if (names) { names.push(name); } else { exportNames[index] = [name]; } } private _setName( names: string[], index: number, name: string, isNameSectionName: boolean ) { if (!name) return; if (isNameSectionName) { if (!isValidName(name)) return; names[index] = name; } else if (!names[index]) { names[index] = name.replace(INVALID_NAME_SYMBOLS_REGEX_GLOBAL, "_"); } } public read(reader: BinaryReader): boolean { if (this._done) throw new Error( "Invalid state: disassembly process was already finished." ); while (true) { if (!reader.read()) return false; switch (reader.state) { case BinaryReaderState.END_WASM: if (!reader.hasMoreBytes()) { this._done = true; return true; } break; case BinaryReaderState.ERROR: throw reader.error; case BinaryReaderState.BEGIN_WASM: this._functionImportsCount = 0; this._memoryImportsCount = 0; this._tableImportsCount = 0; this._globalImportsCount = 0; this._tagImportsCount = 0; this._functionNames = []; this._functionLocalNames = []; this._tagNames = []; this._memoryNames = []; this._typeNames = []; this._tableNames = []; this._globalNames = []; this._fieldNames = []; this._functionExportNames = []; this._globalExportNames = []; this._memoryExportNames = []; this._tableExportNames = []; this._tagExportNames = []; break; case BinaryReaderState.END_SECTION: break; case BinaryReaderState.BEGIN_SECTION: var sectionInfo = reader.result; if ( sectionInfo.id === SectionCode.Custom && bytesToString(sectionInfo.name) === NAME_SECTION_NAME ) { break; } switch (sectionInfo.id) { case SectionCode.Import: case SectionCode.Export: break; // reading known section; default: reader.skipSection(); break; } break; case BinaryReaderState.IMPORT_SECTION_ENTRY: var importInfo = reader.result; const importName = `${bytesToString( importInfo.module )}.${bytesToString(importInfo.field)}`; switch (importInfo.kind) { case ExternalKind.Function: this._setName( this._functionNames, this._functionImportsCount++, importName, false ); break; case ExternalKind.Table: this._setName( this._tableNames, this._tableImportsCount++, importName, false ); break; case ExternalKind.Memory: this._setName( this._memoryNames, this._memoryImportsCount++, importName, false ); break; case ExternalKind.Global: this._setName( this._globalNames, this._globalImportsCount++, importName, false ); break; case ExternalKind.Tag: this._setName( this._tagNames, this._tagImportsCount++, importName, false ); default: throw new Error(`Unsupported export ${importInfo.kind}`); } break; case BinaryReaderState.NAME_SECTION_ENTRY: const nameInfo = reader.result; if (nameInfo.type === NameType.Function) { const { names } = nameInfo; names.forEach(({ index, name }) => { this._setName( this._functionNames, index, bytesToString(name), true ); }); } else if (nameInfo.type === NameType.Local) { const { funcs } = nameInfo; funcs.forEach(({ index, locals }) => { const localNames = (this._functionLocalNames[index] = []); locals.forEach(({ index, name }) => { localNames[index] = bytesToString(name); }); }); } else if (nameInfo.type === NameType.Tag) { const { names } = nameInfo; names.forEach(({ index, name }) => { this._setName(this._tagNames, index, bytesToString(name), true); }); } else if (nameInfo.type === NameType.Type) { const { names } = nameInfo; names.forEach(({ index, name }) => { this._setName(this._typeNames, index, bytesToString(name), true); }); } else if (nameInfo.type === NameType.Table) { const { names } = nameInfo; names.forEach(({ index, name }) => { this._setName(this._tableNames, index, bytesToString(name), true); }); } else if (nameInfo.type === NameType.Memory) { const { names } = nameInfo; names.forEach(({ index, name }) => { this._setName( this._memoryNames, index, bytesToString(name), true ); }); } else if (nameInfo.type === NameType.Global) { const { names } = nameInfo; names.forEach(({ index, name }) => { this._setName( this._globalNames, index, bytesToString(name), true ); }); } else if (nameInfo.type === NameType.Field) { const { types } = nameInfo; types.forEach(({ index, fields }) => { const fieldNames = (this._fieldNames[index] = []); fields.forEach(({ index, name }) => { fieldNames[index] = bytesToString(name); }); }); } break; case BinaryReaderState.EXPORT_SECTION_ENTRY: var exportInfo = reader.result; const exportName = bytesToString(exportInfo.field); switch (exportInfo.kind) { case ExternalKind.Function: this._addExportName( this._functionExportNames, exportInfo.index, exportName ); this._setName( this._functionNames, exportInfo.index, exportName, false ); break; case ExternalKind.Global: this._addExportName( this._globalExportNames, exportInfo.index, exportName ); this._setName( this._globalNames, exportInfo.index, exportName, false ); break; case ExternalKind.Memory: this._addExportName( this._memoryExportNames, exportInfo.index, exportName ); this._setName( this._memoryNames, exportInfo.index, exportName, false ); break; case ExternalKind.Table: this._addExportName( this._tableExportNames, exportInfo.index, exportName ); this._setName( this._tableNames, exportInfo.index, exportName, false ); break; case ExternalKind.Tag: this._addExportName( this._tagExportNames, exportInfo.index, exportName ); this._setName( this._tagNames, exportInfo.index, exportName, false ); break; default: throw new Error(`Unsupported export ${exportInfo.kind}`); } break; default: throw new Error(`Expectected state: ${reader.state}`); } } } public getExportMetadata(): IExportMetadata { return new DevToolsExportMetadata( this._functionExportNames, this._globalExportNames, this._memoryExportNames, this._tableExportNames, this._tagExportNames ); } public getNameResolver(): INameResolver { return new DevToolsNameResolver( this._functionNames, this._functionLocalNames, this._tagNames, this._typeNames, this._tableNames, this._memoryNames, this._globalNames, this._fieldNames ); } }