chrome-devtools-frontend
Version:
Chrome DevTools UI
283 lines (249 loc) • 10.9 kB
text/typescript
// 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});