UNPKG

chrome-devtools-frontend

Version:
283 lines (249 loc) • 10.9 kB
// Copyright 2023 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import { CustomFormatters, type LazyObject, PrimitiveLazyObject, type TypeInfo, type Value, type WasmInterface, } from './CustomFormatters.js'; import type {ForeignObject} from './WasmTypes.js'; /* * Numbers */ CustomFormatters.addFormatter({types: ['bool'], format: (wasm, value) => value.asUint8() > 0}); CustomFormatters.addFormatter({types: ['uint16_t'], format: (wasm, value) => value.asUint16()}); CustomFormatters.addFormatter({types: ['uint32_t'], format: (wasm, value) => value.asUint32()}); CustomFormatters.addFormatter({types: ['uint64_t'], format: (wasm, value) => value.asUint64()}); CustomFormatters.addFormatter({types: ['int16_t'], format: (wasm, value) => value.asInt16()}); CustomFormatters.addFormatter({types: ['int32_t'], format: (wasm, value) => value.asInt32()}); CustomFormatters.addFormatter({types: ['int64_t'], format: (wasm, value) => value.asInt64()}); CustomFormatters.addFormatter({types: ['float'], format: (wasm, value) => value.asFloat32()}); CustomFormatters.addFormatter({types: ['double'], format: (wasm, value) => value.asFloat64()}); export const enum Constants { MAX_STRING_LEN = (1 << 28) - 16, // This is the maximum string len for 32bit taken from V8 PAGE_SIZE = 1 << 12, // Block size used for formatting strings when searching for the null terminator SAFE_HEAP_START = 1 << 10, } export function formatVoid(): () => LazyObject { return () => new PrimitiveLazyObject('undefined', undefined, '<void>'); } CustomFormatters.addFormatter({types: ['void'], format: formatVoid}); CustomFormatters.addFormatter({types: ['uint8_t', 'int8_t'], format: formatChar}); export function formatChar(wasm: WasmInterface, value: Value): string { const char = value.typeNames.includes('int8_t') ? Math.abs(value.asInt8()) : value.asUint8(); switch (char) { case 0x0: return '\'\\0\''; case 0x7: return '\'\\a\''; case 0x8: return '\'\\b\''; case 0x9: return '\'\\t\''; case 0xA: return '\'\\n\''; case 0xB: return '\'\\v\''; case 0xC: return '\'\\f\''; case 0xD: return '\'\\r\''; } if (char < 0x20 || char > 0x7e) { return `'\\x${char.toString(16).padStart(2, '0')}'`; } return `'${String.fromCharCode(value.asInt8())}'`; } CustomFormatters.addFormatter({ types: ['wchar_t', 'char32_t', 'char16_t'], format: (wasm, value) => { const codepoint = value.size === 2 ? value.asUint16() : value.asUint32(); try { return String.fromCodePoint(codepoint); } catch { return `U+${codepoint.toString(16).padStart(value.size * 2, '0')}`; } }, }); /* * STL */ function formatLibCXXString<T extends CharArrayConstructor>( wasm: WasmInterface, value: Value, charType: T, decode: (chars: InstanceType<T>) => string): {size: number, string: string} { const shortString = value.$('__r_.__value_.<union>.__s'); const size = shortString.getMembers().includes('<union>') ? shortString.$('<union>.__size_').asUint8() : shortString.$('__size_').asUint8(); const isLong = 0 < (size & 0x80); const charSize = charType.BYTES_PER_ELEMENT; if (isLong) { const longString = value.$('__r_.__value_.<union>.__l'); const data = longString.$('__data_').asUint32(); const stringSize = longString.$('__size_').asUint32(); const copyLen = Math.min(stringSize * charSize, Constants.MAX_STRING_LEN); const bytes = wasm.readMemory(data, copyLen); const text = new charType(bytes.buffer, bytes.byteOffset, stringSize) as InstanceType<T>; return {size: stringSize, string: decode(text)}; } const bytes = shortString.$('__data_').asDataView(0, size * charSize); const text = new charType(bytes.buffer, bytes.byteOffset, size) as InstanceType<T>; return {size, string: decode(text)}; } export function formatLibCXX8String(wasm: WasmInterface, value: Value): {size: number, string: string} { return formatLibCXXString(wasm, value, Uint8Array, str => new TextDecoder().decode(str)); } export function formatLibCXX16String(wasm: WasmInterface, value: Value): {size: number, string: string} { return formatLibCXXString(wasm, value, Uint16Array, str => new TextDecoder('utf-16le').decode(str)); } export function formatLibCXX32String(wasm: WasmInterface, value: Value): {size: number, string: string} { // emscripten's wchar is 4 byte return formatLibCXXString( wasm, value, Uint32Array, str => Array.from(str).map(v => String.fromCodePoint(v)).join('')); } CustomFormatters.addFormatter({ types: [ 'std::__2::string', 'std::__2::basic_string<char, std::__2::char_traits<char>, std::__2::allocator<char> >', 'std::__2::u8string', 'std::__2::basic_string<char8_t, std::__2::char_traits<char8_t>, std::__2::allocator<char8_t> >', ], format: formatLibCXX8String, }); CustomFormatters.addFormatter({ types: [ 'std::__2::u16string', 'std::__2::basic_string<char16_t, std::__2::char_traits<char16_t>, std::__2::allocator<char16_t> >', ], format: formatLibCXX16String, }); CustomFormatters.addFormatter({ types: [ 'std::__2::wstring', 'std::__2::basic_string<wchar_t, std::__2::char_traits<wchar_t>, std::__2::allocator<wchar_t> >', 'std::__2::u32string', 'std::__2::basic_string<char32_t, std::__2::char_traits<char32_t>, std::__2::allocator<char32_t> >', ], format: formatLibCXX32String, }); type CharArrayConstructor = Uint8ArrayConstructor|Uint16ArrayConstructor|Uint32ArrayConstructor; function formatRawString<T extends CharArrayConstructor>( wasm: WasmInterface, value: Value, charType: T, decode: (chars: InstanceType<T>) => string, ): string|Record<string, Value|null> { const address = value.asUint32(); if (address < Constants.SAFE_HEAP_START) { return formatPointerOrReference(wasm, value); } const charSize = charType.BYTES_PER_ELEMENT; const slices: DataView[] = []; const deref = value.$('*'); for (let bufferSize = 0; bufferSize < Constants.MAX_STRING_LEN; bufferSize += Constants.PAGE_SIZE) { // Copy PAGE_SIZE bytes const buffer = deref.asDataView(bufferSize, Constants.PAGE_SIZE); // Convert to charType const substr = new charType(buffer.buffer, buffer.byteOffset, buffer.byteLength / charSize); const strlen = substr.indexOf(0); if (strlen >= 0) { // buffer size is in bytes, strlen in characters const str = new charType(bufferSize / charSize + strlen) as InstanceType<T>; for (let i = 0; i < slices.length; ++i) { str.set( // @ts-expect-error TypeScript can't find the deduce the intersection type correctly new charType(slices[i].buffer, slices[i].byteOffset, slices[i].byteLength / charSize), i * Constants.PAGE_SIZE / charSize); } str.set(substr.subarray(0, strlen), bufferSize / charSize); return decode(str); } slices.push(buffer); } return formatPointerOrReference(wasm, value); } export function formatCString(wasm: WasmInterface, value: Value): string|Record<string, Value|null> { return formatRawString(wasm, value, Uint8Array, str => new TextDecoder().decode(str)); } export function formatU16CString(wasm: WasmInterface, value: Value): string|Record<string, Value|null> { return formatRawString(wasm, value, Uint16Array, str => new TextDecoder('utf-16le').decode(str)); } export function formatCWString(wasm: WasmInterface, value: Value): string|Record<string, Value|null> { // emscripten's wchar is 4 byte return formatRawString(wasm, value, Uint32Array, str => Array.from(str).map(v => String.fromCodePoint(v)).join('')); } // Register with higher precedence than the generic pointer handler. CustomFormatters.addFormatter({types: ['char *', 'char8_t *'], format: formatCString}); CustomFormatters.addFormatter({types: ['char16_t *'], format: formatU16CString}); CustomFormatters.addFormatter({types: ['wchar_t *', 'char32_t *'], format: formatCWString}); export function formatVector(wasm: WasmInterface, value: Value): Value[] { const begin = value.$('__begin_'); const end = value.$('__end_'); const size = (end.asUint32() - begin.asUint32()) / begin.$('*').size; const elements = []; for (let i = 0; i < size; ++i) { elements.push(begin.$(i)); } return elements; } function reMatch(...exprs: RegExp[]): (type: TypeInfo) => boolean { return (type: TypeInfo) => { for (const expr of exprs) { for (const name of type.typeNames) { if (expr.exec(name)) { return true; } } } for (const expr of exprs) { for (const name of type.typeNames) { if (name.startsWith('const ')) { if (expr.exec(name.substring(6))) { return true; } } } } return false; }; } CustomFormatters.addFormatter({types: reMatch(/^std::vector<.+>$/), format: formatVector}); export function formatPointerOrReference(wasm: WasmInterface, value: Value): Record<string, Value|null> { const address = value.asUint32(); if (address === 0) { return {'0x0': null}; } return {[`0x${address.toString(16)}`]: value.$('*')}; } CustomFormatters.addFormatter({types: type => type.isPointer, format: formatPointerOrReference}); export function formatDynamicArray(wasm: WasmInterface, value: Value): Record<string, Value|null> { return {[`0x${value.location.toString(16)}`]: value.$(0)}; } CustomFormatters.addFormatter({types: reMatch(/^.+\[\]$/), format: formatDynamicArray}); export function formatUInt128(wasm: WasmInterface, value: Value): bigint { const view = value.asDataView(); return (view.getBigUint64(8, true) << BigInt(64)) + (view.getBigUint64(0, true)); } CustomFormatters.addFormatter({types: ['unsigned __int128'], format: formatUInt128}); export function formatInt128(wasm: WasmInterface, value: Value): bigint { const view = value.asDataView(); return (view.getBigInt64(8, true) << BigInt(64)) | (view.getBigUint64(0, true)); } CustomFormatters.addFormatter({types: ['__int128'], format: formatInt128}); export function formatExternRef(wasm: WasmInterface, value: Value): () => LazyObject { const obj = { async getProperties(): Promise<Array<{name: string, property: LazyObject}>> { return []; }, async asRemoteObject(): Promise<ForeignObject> { const encodedValue = value.asUint64(); const ValueClasses: ['global', 'local', 'operand'] = ['global', 'local', 'operand']; const valueClass = ValueClasses[Number(encodedValue >> 32n)]; return {type: 'reftype', valueClass, index: Number(BigInt.asUintN(32, encodedValue))}; } }; return () => obj; } CustomFormatters.addFormatter({types: ['__externref_t', 'externref_t'], format: formatExternRef});