first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

35
node_modules/metro-symbolicate/package.json generated vendored Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "metro-symbolicate",
"version": "0.83.3",
"description": "🚇 A tool to find the source location from JS bundles and stack traces.",
"license": "MIT",
"main": "./src/index.js",
"bin": "./src/index.js",
"exports": {
".": "./src/index.js",
"./package.json": "./package.json",
"./private/*": "./src/*.js"
},
"repository": {
"type": "git",
"url": "git@github.com:facebook/metro.git"
},
"scripts": {
"prepare-release": "test -d build && rm -rf src.real && mv src src.real && mv build src",
"cleanup-release": "test ! -e build && mv src build && mv src.real src"
},
"keywords": [
"metro"
],
"dependencies": {
"flow-enums-runtime": "^0.0.6",
"invariant": "^2.2.4",
"metro-source-map": "0.83.3",
"nullthrows": "^1.1.1",
"source-map": "^0.5.6",
"vlq": "^1.0.0"
},
"engines": {
"node": ">=20.19.4"
}
}

View File

@@ -0,0 +1,367 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.ChromeHeapSnapshotProcessor = void 0;
var _invariant = _interopRequireDefault(require("invariant"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const CHILDREN_FIELD_TYPE = "__CHILDREN__";
class ChromeHeapSnapshotProcessor {
constructor(snapshotData) {
this._snapshotData = snapshotData;
this._globalStringTable = new ChromeHeapSnapshotStringTable(
this._snapshotData.strings,
);
}
traceFunctionInfos() {
return new ChromeHeapSnapshotRecordIterator(
this._snapshotData.trace_function_infos,
this._snapshotData.snapshot.meta.trace_function_info_fields,
{
name: "string",
script_name: "string",
},
this._globalStringTable,
undefined,
);
}
locations() {
return new ChromeHeapSnapshotRecordIterator(
this._snapshotData.locations,
this._snapshotData.snapshot.meta.location_fields,
null,
this._globalStringTable,
undefined,
);
}
nodes() {
return new ChromeHeapSnapshotRecordIterator(
this._snapshotData.nodes,
this._snapshotData.snapshot.meta.node_fields,
this._snapshotData.snapshot.meta.node_types,
this._globalStringTable,
undefined,
);
}
edges() {
return new ChromeHeapSnapshotRecordIterator(
this._snapshotData.edges,
this._snapshotData.snapshot.meta.edge_fields,
this._snapshotData.snapshot.meta.edge_types,
this._globalStringTable,
undefined,
);
}
traceTree() {
return new ChromeHeapSnapshotRecordIterator(
this._snapshotData.trace_tree,
this._snapshotData.snapshot.meta.trace_node_fields,
{
children: CHILDREN_FIELD_TYPE,
},
this._globalStringTable,
undefined,
);
}
}
exports.ChromeHeapSnapshotProcessor = ChromeHeapSnapshotProcessor;
class ChromeHeapSnapshotStringTable {
constructor(strings) {
this._strings = strings;
this._indexCache = new Map();
}
add(value) {
this._syncIndexCache();
let index = this._indexCache.get(value);
if (index != null) {
return index;
}
index = this._strings.length;
this._strings.push(value);
this._indexCache.set(value, index);
return index;
}
get(index) {
(0, _invariant.default)(
index >= 0 && index < this._strings.length,
"index out of string table range",
);
return this._strings[index];
}
_syncIndexCache() {
if (this._strings.length > this._indexCache.size) {
for (let i = this._indexCache.size; i < this._strings.length; ++i) {
this._indexCache.set(this._strings[i], i);
}
}
}
}
class ChromeHeapSnapshotRecordAccessor {
constructor(
buffer,
recordFields,
recordTypes,
globalStringTable,
position,
parent,
) {
if (parent) {
this._recordSize = parent._recordSize;
this._fieldToOffset = parent._fieldToOffset;
this._fieldToType = parent._fieldToType;
} else {
this._recordSize = recordFields.length;
this._fieldToOffset = new Map(
Object.entries(recordFields).map(([offsetStr, name]) => [
String(name),
Number(offsetStr),
]),
);
if (Array.isArray(recordTypes)) {
this._fieldToType = new Map(
Object.entries(recordTypes).map(([offsetStr, type]) => [
recordFields[Number(offsetStr)],
type,
]),
);
} else {
this._fieldToType = new Map(Object.entries(recordTypes || {}));
}
}
this._buffer = buffer;
this._position = position;
(0, _invariant.default)(
this._position % this._recordSize === 0,
"Record accessor constructed at invalid offset",
);
(0, _invariant.default)(
this._buffer.length % this._recordSize === 0,
"Record accessor constructed with wrong size buffer",
);
this._globalStringTable = globalStringTable;
}
getString(field) {
const dynamicValue = this._getScalar(field);
if (typeof dynamicValue === "string") {
return dynamicValue;
}
throw new Error("Not a string or enum field: " + field);
}
getNumber(field) {
const dynamicValue = this._getScalar(field);
if (typeof dynamicValue === "number") {
return dynamicValue;
}
throw new Error("Not a number field: " + field);
}
getChildren(field) {
const fieldType = this._fieldToType.get(field);
if (fieldType !== CHILDREN_FIELD_TYPE) {
throw new Error("Not a children field: " + field);
}
const childrenBuffer = this._getRaw(field);
(0, _invariant.default)(
Array.isArray(childrenBuffer),
"Expected array in children-typed field",
);
return new ChromeHeapSnapshotRecordIterator(
childrenBuffer,
[],
null,
this._globalStringTable,
-this._fieldToOffset.size,
this,
);
}
setString(field, value) {
this._setRaw(field, this._encodeString(field, value));
}
setNumber(field, value) {
const fieldType = this._fieldToType.get(field);
if (
Array.isArray(fieldType) ||
fieldType === "string" ||
fieldType === CHILDREN_FIELD_TYPE
) {
throw new Error("Not a number field: " + field);
}
this._setRaw(field, value);
}
moveToRecord(recordIndex) {
this._moveToPosition(recordIndex * this._recordSize);
}
append(record) {
const savedPosition = this._position;
try {
return this.moveAndInsert(this._buffer.length / this._recordSize, record);
} finally {
this._position = savedPosition;
}
}
moveAndInsert(recordIndex, record) {
this._moveToPosition(recordIndex * this._recordSize, true);
let didResizeBuffer = false;
try {
for (const field of this._fieldToOffset.keys()) {
if (!Object.prototype.hasOwnProperty.call(record, field)) {
throw new Error("Missing value for field: " + field);
}
}
this._buffer.splice(this._position, 0, ...new Array(this._recordSize));
didResizeBuffer = true;
for (const field of Object.keys(record)) {
this._set(field, record[field]);
}
return this._position / this._recordSize;
} catch (e) {
if (didResizeBuffer) {
this._buffer.splice(this._position, this._recordSize);
}
throw e;
}
}
protectedHasNext() {
if (this._position < 0) {
return this._buffer.length > 0;
}
return this._position < this._buffer.length;
}
protectedTryMoveNext() {
if (this.protectedHasNext()) {
this._moveToPosition(this._position + this._recordSize, true);
}
}
_getRaw(field) {
this._validatePosition();
const offset = this._fieldToOffset.get(field);
if (offset == null) {
throw new Error("Unknown field: " + field);
}
return this._buffer[this._position + offset];
}
_getScalar(field) {
const rawValue = this._getRaw(field);
if (Array.isArray(rawValue)) {
throw new Error("Not a scalar field: " + field);
}
const fieldType = this._fieldToType.get(field);
if (Array.isArray(fieldType)) {
(0, _invariant.default)(
rawValue >= 0 && rawValue < fieldType.length,
"raw value does not match field enum type",
);
return fieldType[rawValue];
}
if (fieldType === "string") {
return this._globalStringTable.get(rawValue);
}
return rawValue;
}
_setRaw(field, rawValue) {
this._validatePosition();
const offset = this._fieldToOffset.get(field);
if (offset == null) {
throw new Error("Unknown field: " + field);
}
this._buffer[this._position + offset] = rawValue;
}
_set(field, value) {
if (typeof value === "string") {
this.setString(field, value);
} else if (typeof value === "number") {
this.setNumber(field, value);
} else if (Array.isArray(value)) {
this._setChildren(field, value);
} else {
throw new Error("Unsupported value for field: " + field);
}
}
_setChildren(field, value) {
const fieldType = this._fieldToType.get(field);
if (fieldType !== CHILDREN_FIELD_TYPE) {
throw new Error("Not a children field: " + field);
}
this._setRaw(field, []);
const childIt = this.getChildren(field);
for (const child of value) {
childIt.append(child);
}
}
_encodeString(field, value) {
const fieldType = this._fieldToType.get(field);
if (Array.isArray(fieldType)) {
const index = fieldType.indexOf(value);
(0, _invariant.default)(
index >= 0,
"Cannot define new values in enum field",
);
return index;
}
if (fieldType === "string") {
return this._globalStringTable.add(value);
}
throw new Error("Not a string or enum field: " + field);
}
_validatePosition(allowEnd = false, position = this._position) {
if (!Number.isInteger(position)) {
throw new Error(`Position ${position} is not an integer`);
}
if (position % this._recordSize !== 0) {
throw new Error(
`Position ${position} is not a multiple of record size ${this._recordSize}`,
);
}
if (position < 0) {
throw new Error(`Position ${position} is out of range`);
}
const maxPosition = allowEnd
? this._buffer.length
: this._buffer.length - 1;
if (position > maxPosition) {
throw new Error(`Position ${position} is out of range`);
}
if (this._buffer.length - position < this._recordSize) {
if (!(allowEnd && this._buffer.length === position)) {
throw new Error(
`Record at position ${position} is truncated: expected ${this._recordSize} fields but found ${this._buffer.length - position}`,
);
}
}
}
_moveToPosition(nextPosition, allowEnd = false) {
this._validatePosition(allowEnd, nextPosition);
this._position = nextPosition;
}
}
class ChromeHeapSnapshotRecordIterator extends ChromeHeapSnapshotRecordAccessor {
constructor(
buffer,
recordFields,
recordTypes,
globalStringTable,
position = -recordFields.length,
parent,
) {
super(
buffer,
recordFields,
recordTypes,
globalStringTable,
position,
parent,
);
}
next() {
this.protectedTryMoveNext();
return {
done: !this.protectedHasNext(),
value: this,
};
}
[Symbol.iterator]() {
return this;
}
}

View File

@@ -0,0 +1,611 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/
import invariant from 'invariant';
type RawBuffer = Array<number | RawBuffer>;
export type ChromeHeapSnapshot = {
snapshot: {
meta: {
trace_function_info_fields: Array<string>,
location_fields: Array<string>,
edge_fields: Array<string>,
edge_types: Array<string | Array<string>>,
node_fields: Array<string>,
node_types: Array<string | Array<string>>,
trace_node_fields: Array<string>,
...
},
...
},
trace_function_infos: Array<number>,
locations: Array<number>,
edges: Array<number>,
nodes: Array<number>,
strings: Array<string>,
trace_tree: RawBuffer,
...
};
// The snapshot metadata doesn't have a type describing the `children` field
// of `trace_tree`, but modeling it as a type works really well. So we make up
// our own name for it and use that internally.
const CHILDREN_FIELD_TYPE = '__CHILDREN__';
// An adapter for reading and mutating a Chrome heap snapshot in-place,
// including safely decoding and encoding fields that point into the global
// string table and into enum types.
// Care is taken to adhere to the self-describing heap snapshot schema, but
// we make some additional assumptions based on what Chrome hardcodes (where
// the format leaves us no other choice).
export class ChromeHeapSnapshotProcessor {
// The raw snapshot data provided to this processor. Mutable.
+_snapshotData: ChromeHeapSnapshot;
// An adapter for the global string table in the raw snapshot data.
// This is shared across all the iterators we will create.
+_globalStringTable: ChromeHeapSnapshotStringTable;
constructor(snapshotData: ChromeHeapSnapshot) {
this._snapshotData = snapshotData;
this._globalStringTable = new ChromeHeapSnapshotStringTable(
this._snapshotData.strings,
);
}
traceFunctionInfos(): ChromeHeapSnapshotRecordIterator {
return new ChromeHeapSnapshotRecordIterator(
// Flow is being conservative here, but we'll never change a number into RawBuffer or vice versa.
// $FlowFixMe[incompatible-type]
this._snapshotData.trace_function_infos,
this._snapshotData.snapshot.meta.trace_function_info_fields,
{name: 'string', script_name: 'string'},
this._globalStringTable,
undefined /* start position */,
);
}
locations(): ChromeHeapSnapshotRecordIterator {
return new ChromeHeapSnapshotRecordIterator(
// Flow is being conservative here, but we'll never change a number into RawBuffer or vice versa.
// $FlowFixMe[incompatible-type]
this._snapshotData.locations,
this._snapshotData.snapshot.meta.location_fields,
null,
this._globalStringTable,
undefined /* start position */,
);
}
nodes(): ChromeHeapSnapshotRecordIterator {
return new ChromeHeapSnapshotRecordIterator(
// Flow is being conservative here, but we'll never change a number into RawBuffer or vice versa.
// $FlowFixMe[incompatible-type]
this._snapshotData.nodes,
this._snapshotData.snapshot.meta.node_fields,
this._snapshotData.snapshot.meta.node_types,
this._globalStringTable,
undefined /* start position */,
);
}
edges(): ChromeHeapSnapshotRecordIterator {
return new ChromeHeapSnapshotRecordIterator(
// Flow is being conservative here, but we'll never change a number into RawBuffer or vice versa.
// $FlowFixMe[incompatible-type]
this._snapshotData.edges,
this._snapshotData.snapshot.meta.edge_fields,
this._snapshotData.snapshot.meta.edge_types,
this._globalStringTable,
undefined /* start position */,
);
}
traceTree(): ChromeHeapSnapshotRecordIterator {
return new ChromeHeapSnapshotRecordIterator(
this._snapshotData.trace_tree,
this._snapshotData.snapshot.meta.trace_node_fields,
{children: CHILDREN_FIELD_TYPE},
this._globalStringTable,
undefined /* start position */,
);
}
}
// An uniquing adapter for the heap snapshot's string table that allows
// retrieving and adding strings.
//
// Assumptions:
// 1. The string table is only manipulated via this class, and only via a
// single instance of it.
// 2. The string table array is always mutated in-place rather than being
// copied / replaced with a new array in its containing object.
class ChromeHeapSnapshotStringTable {
+_strings: Array<string>;
+_indexCache: Map<string, number>;
constructor(strings: Array<string>) {
this._strings = strings;
this._indexCache = new Map();
// NOTE: _indexCache is lazily initialised in _syncIndexCache.
}
// Looks up a string in the string table, adds it if necessary, and returns
// its index.
add(value: string): number {
this._syncIndexCache();
let index = this._indexCache.get(value);
if (index != null) {
return index;
}
index = this._strings.length;
this._strings.push(value);
this._indexCache.set(value, index);
return index;
}
// Retrieve the string at the given index.
get(index: number): string {
invariant(
index >= 0 && index < this._strings.length,
'index out of string table range',
);
return this._strings[index];
}
// Indexes the string table for fast lookup.
_syncIndexCache() {
// Because we only grow the string table and we assume it's unique to begin
// with, we only need to scan any strings that we may have appended since
// the last time we synced the index.
// NOTE: This is not even strictly necessary other than for the very first
// add() call, but it might allow us to do more complicated string table
// manipulation down the line.
if (this._strings.length > this._indexCache.size) {
for (let i = this._indexCache.size; i < this._strings.length; ++i) {
this._indexCache.set(this._strings[i], i);
}
}
}
}
type ChromeHeapSnapshotFieldType =
// enum
| Array<string>
// type name
| string;
// The input type to functions that accept record objects.
type DenormalizedRecordInput = $ReadOnly<{
[field: string]: string | number | $ReadOnlyArray<DenormalizedRecordInput>,
}>;
// A cursor pointing to a record-aligned position in a 1D array of N records
// each with K fields in a fixed order. Supports encoding/decoding field values
// in the raw array according to a schema passed to the constructor.
//
// Field values are stored as either numbers (representing scalars) or arrays
// (representing lists of nested records). Scalar fields may represent strings
// in the string table, strings in an enum, or numbers. Nested record lists are
// processed according to the same schema as their parent record.
//
// Setters directly mutate raw data in the buffer and in the string table.
class ChromeHeapSnapshotRecordAccessor {
// Fast lookup tables from field names to their offsets (required) and types
// (optional). These are shared with any child iterators.
+_fieldToOffset: $ReadOnlyMap<string, number>;
+_fieldToType: $ReadOnlyMap<string, ChromeHeapSnapshotFieldType>;
// The number of fields in every record (i.e. K).
+_recordSize: number;
// The raw buffer. Mutable.
+_buffer: RawBuffer;
// The global string table. Mutable in the ways allowed by the string table
// class.
+_globalStringTable: ChromeHeapSnapshotStringTable;
// The current position in the raw buffer.
_position: number;
constructor(
buffer: RawBuffer,
recordFields: Array<string>,
// recordTypes can be:
// 1. An array: Field types as described in the snapshot itself, e.g.
// node_types, edge_types.
// 2. An object: Field types that are implicit (hardcoded in V8 / DevTools)
// so we pass them in by field name.
// 3. null: No field types are known.
// Fields with unknown types are assumed to be numeric.
recordTypes:
| Array<ChromeHeapSnapshotFieldType>
| $ReadOnly<{
[string]: ChromeHeapSnapshotFieldType,
}>
| null,
globalStringTable: ChromeHeapSnapshotStringTable,
position: number,
parent?: ChromeHeapSnapshotRecordAccessor,
) {
if (parent) {
this._recordSize = parent._recordSize;
this._fieldToOffset = parent._fieldToOffset;
this._fieldToType = parent._fieldToType;
} else {
this._recordSize = recordFields.length;
this._fieldToOffset = new Map(
// $FlowFixMe[not-an-object]
Object.entries(recordFields).map(([offsetStr, name]) => [
String(name),
Number(offsetStr),
]),
);
if (Array.isArray(recordTypes)) {
this._fieldToType = new Map<string, ChromeHeapSnapshotFieldType>(
// $FlowFixMe[not-an-object]
Object.entries(recordTypes).map(([offsetStr, type]) => [
recordFields[Number(offsetStr)],
type,
]),
);
} else {
// $FlowFixMe[incompatible-type] Object.entries is incompletely typed
this._fieldToType = new Map(Object.entries(recordTypes || {}));
}
}
this._buffer = buffer;
this._position = position;
invariant(
this._position % this._recordSize === 0,
'Record accessor constructed at invalid offset',
);
invariant(
this._buffer.length % this._recordSize === 0,
'Record accessor constructed with wrong size buffer',
);
this._globalStringTable = globalStringTable;
}
/** Public API */
// Reads a scalar string or enum value from the given field.
// It's an error to read a number (or other non-string) field as a string.
// NOTE: The type "string_or_number" is always treated as a number and cannot
// be read using this method.
getString(field: string): string {
const dynamicValue = this._getScalar(field);
if (typeof dynamicValue === 'string') {
return dynamicValue;
}
throw new Error('Not a string or enum field: ' + field);
}
// Reads a scalar numeric value from the given field.
// It's an error to read a string (or other non-number) field as a number.
// NOTE: The type "string_or_number" is always treated as a number.
getNumber(field: string): number {
const dynamicValue = this._getScalar(field);
if (typeof dynamicValue === 'number') {
return dynamicValue;
}
throw new Error('Not a number field: ' + field);
}
// Returns an iterator over the children of this record that are stored in
// the given field (typically 'children'). Children conform to the same
// schema as the current record.
getChildren(field: string): ChromeHeapSnapshotRecordIterator {
const fieldType = this._fieldToType.get(field);
if (fieldType !== CHILDREN_FIELD_TYPE) {
throw new Error('Not a children field: ' + field);
}
const childrenBuffer = this._getRaw(field);
invariant(
Array.isArray(childrenBuffer),
'Expected array in children-typed field',
);
return new ChromeHeapSnapshotRecordIterator(
childrenBuffer,
[], // recordFields ignored when there's a parent
null, // recordTypes ignored when there's a parent
this._globalStringTable,
-this._fieldToOffset.size /* start position */,
this,
);
}
// Writes a scalar string or enum value into the given field, updating the
// global string table as needed.
// It's an error to write anything other than a string into a string or enum
// field.
// It's an error to write an unknown enum value into an enum field.
// NOTE: The type "string_or_number" is always treated as a number and cannot
// be written using this method.
setString(field: string, value: string): void {
this._setRaw(field, this._encodeString(field, value));
}
// Writes a scalar numeric value into the given field.
// It's an error to write anything other than a number into a numeric field.
// NOTE: The type "string_or_number" is always treated as a number.
setNumber(field: string, value: number): void {
const fieldType = this._fieldToType.get(field);
if (
Array.isArray(fieldType) ||
fieldType === 'string' ||
fieldType === CHILDREN_FIELD_TYPE
) {
throw new Error('Not a number field: ' + field);
}
this._setRaw(field, value);
}
// Moves the cursor to a given index in the buffer (expressed in # of
// records, NOT fields).
moveToRecord(recordIndex: number) {
this._moveToPosition(recordIndex * this._recordSize);
}
// Appends a new record at the end of the buffer.
//
// Returns the index of the appended record. All fields must be specified and
// have values of the correct types. The cursor may move while writing, but
// is guaranteed to return to its initial position when this function returns
// (or throws).
append(record: DenormalizedRecordInput): number {
const savedPosition = this._position;
try {
return this.moveAndInsert(this._buffer.length / this._recordSize, record);
} finally {
this._position = savedPosition;
}
}
// Moves the cursor to a given index in the buffer (expressed in # of
// records, NOT fields) and inserts a record.
//
// Returns the index of the inserted record. All fields must be specified and
// have values of the correct types. The given index may be the end of the
// buffer; otherwise existing records starting at the given index will be
// shifted to the right to accommodate the new record.
//
// NOTE: Inserting is a risky, low-level operation. Care must be taken not to
// desync buffers that implicitly or explicitly depend on one another (e.g.
// edge.to_node -> node position, cumulative node.edge_count -> edge indices).
moveAndInsert(recordIndex: number, record: DenormalizedRecordInput): number {
this._moveToPosition(recordIndex * this._recordSize, /* allowEnd */ true);
let didResizeBuffer = false;
try {
for (const field of this._fieldToOffset.keys()) {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
if (!Object.prototype.hasOwnProperty.call(record, field)) {
throw new Error('Missing value for field: ' + field);
}
}
this._buffer.splice(
this._position,
0,
...new Array<number | RawBuffer>(this._recordSize),
);
didResizeBuffer = true;
for (const field of Object.keys(record)) {
this._set(field, record[field]);
}
return this._position / this._recordSize;
} catch (e) {
if (didResizeBuffer) {
// Roll back the write
this._buffer.splice(this._position, this._recordSize);
}
throw e;
}
}
/** "Protected" methods (please don't use) */
// Return true if we can advance the position by one record (including from
// the last record to the "end" position).
protectedHasNext(): boolean {
if (this._position < 0) {
// We haven't started iterating yet, so this might _be_ the end position.
return this._buffer.length > 0;
}
return this._position < this._buffer.length;
}
// Move to the next record (or the end) if we're not already at the end.
protectedTryMoveNext(): void {
if (this.protectedHasNext()) {
this._moveToPosition(
this._position + this._recordSize,
/* allowEnd */ true,
);
}
}
/** Private methods */
// Reads the raw numeric value of a field.
_getRaw(field: string): number | RawBuffer {
this._validatePosition();
const offset = this._fieldToOffset.get(field);
if (offset == null) {
throw new Error('Unknown field: ' + field);
}
return this._buffer[this._position + offset];
}
// Decodes a scalar (string or number) field.
_getScalar(field: string): string | number {
const rawValue = this._getRaw(field);
if (Array.isArray(rawValue)) {
throw new Error('Not a scalar field: ' + field);
}
const fieldType = this._fieldToType.get(field);
if (Array.isArray(fieldType)) {
invariant(
rawValue >= 0 && rawValue < fieldType.length,
'raw value does not match field enum type',
);
return fieldType[rawValue];
}
if (fieldType === 'string') {
return this._globalStringTable.get(rawValue);
}
return rawValue;
}
// Writes the raw value of a field.
_setRaw(field: string, rawValue: number | RawBuffer): void {
this._validatePosition();
const offset = this._fieldToOffset.get(field);
if (offset == null) {
throw new Error('Unknown field: ' + field);
}
this._buffer[this._position + offset] = rawValue;
}
// Writes a scalar or children value to `field`, inferring the intended type
// based on the runtime type of `value`.
_set(
field: string,
value: string | number | $ReadOnlyArray<DenormalizedRecordInput>,
): void {
if (typeof value === 'string') {
this.setString(field, value);
} else if (typeof value === 'number') {
this.setNumber(field, value);
} else if (Array.isArray(value)) {
this._setChildren(field, value);
} else {
throw new Error('Unsupported value for field: ' + field);
}
}
// Writes a children array to `field` by appending each element of `value` to
// a new buffer using `append()`s semantics.
_setChildren(
field: string,
value: $ReadOnlyArray<DenormalizedRecordInput>,
): void {
const fieldType = this._fieldToType.get(field);
if (fieldType !== CHILDREN_FIELD_TYPE) {
throw new Error('Not a children field: ' + field);
}
this._setRaw(field, []);
const childIt = this.getChildren(field);
for (const child of value) {
childIt.append(child);
}
}
// Encodes a string value according to its field schema.
// The global string table may be updated as a side effect.
_encodeString(field: string, value: string): number {
const fieldType = this._fieldToType.get(field);
if (Array.isArray(fieldType)) {
const index = fieldType.indexOf(value);
invariant(index >= 0, 'Cannot define new values in enum field');
return index;
}
if (fieldType === 'string') {
return this._globalStringTable.add(value);
}
throw new Error('Not a string or enum field: ' + field);
}
// Asserts that the given position (default: the current position) is either
// a valid position for reading a record, or (if allowEnd is true) the end of
// the buffer.
_validatePosition(
allowEnd?: boolean = false,
position?: number = this._position,
): void {
if (!Number.isInteger(position)) {
throw new Error(`Position ${position} is not an integer`);
}
if (position % this._recordSize !== 0) {
throw new Error(
`Position ${position} is not a multiple of record size ${this._recordSize}`,
);
}
if (position < 0) {
throw new Error(`Position ${position} is out of range`);
}
const maxPosition = allowEnd
? this._buffer.length
: this._buffer.length - 1;
if (position > maxPosition) {
throw new Error(`Position ${position} is out of range`);
}
if (this._buffer.length - position < this._recordSize) {
if (!(allowEnd && this._buffer.length === position)) {
throw new Error(
`Record at position ${position} is truncated: expected ${this._recordSize} fields but found ${this._buffer.length - position}`,
);
}
}
}
// Move to the given position or throw an error if it is invalid.
_moveToPosition(nextPosition: number, allowEnd: boolean = false) {
this._validatePosition(allowEnd, nextPosition);
this._position = nextPosition;
}
}
// $FlowFixMe[incompatible-type] Flow doesn't see that we implement the iteration protocol
class ChromeHeapSnapshotRecordIterator
extends ChromeHeapSnapshotRecordAccessor
implements Iterable<ChromeHeapSnapshotRecordAccessor>
{
constructor(
buffer: RawBuffer,
recordFields: Array<string>,
recordTypes:
| Array<ChromeHeapSnapshotFieldType>
| $ReadOnly<{
[string]: ChromeHeapSnapshotFieldType,
}>
| null,
globalStringTable: ChromeHeapSnapshotStringTable,
// Initialise to "before the first iteration".
// The Accessor constructor intentionally checks only alignment, not range,
// so this works as long as we don't try to read/write (at which point
// validation will kick in).
position: number = -recordFields.length,
parent?: ChromeHeapSnapshotRecordAccessor,
) {
super(
buffer,
recordFields,
recordTypes,
globalStringTable,
position,
parent,
);
}
// JS Iterator protocol
next(): {done: boolean, +value: this} {
this.protectedTryMoveNext();
return {done: !this.protectedHasNext(), value: this};
}
// JS Iterable protocol
// $FlowFixMe[unsupported-syntax]
[Symbol.iterator](): this {
return this;
}
}

View File

@@ -0,0 +1,54 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _metroSourceMap = require("metro-source-map");
class GoogleIgnoreListConsumer {
constructor(map, normalizeSourceFn = _metroSourceMap.normalizeSourcePath) {
this._sourceMap = map;
this._normalizeSource = normalizeSourceFn;
}
isIgnored({ source }) {
return source != null && this._getIgnoredSourceSet().has(source);
}
toArray(sources) {
const ignoredSourceSet = this._getIgnoredSourceSet();
const encoded = [];
for (const [sourceIndex, source] of sources.entries()) {
if (source != null && ignoredSourceSet.has(source)) {
encoded.push(sourceIndex);
}
}
return encoded;
}
_getIgnoredSourceSet() {
if (!this._ignoredSourceSet) {
const ignoredSourceSet = new Set();
this._buildIgnoredSourceSet(this._sourceMap, ignoredSourceSet);
this._ignoredSourceSet = ignoredSourceSet;
}
return this._ignoredSourceSet;
}
_buildIgnoredSourceSet(map, ignoredSourceSet) {
if (map.mappings === undefined) {
const indexMap = map;
indexMap.sections.forEach((section) =>
this._buildIgnoredSourceSet(section.map, ignoredSourceSet),
);
return;
}
if ("x_google_ignoreList" in map) {
const basicMap = map;
(basicMap.x_google_ignoreList || []).forEach((sourceIndex) => {
let source = basicMap.sources[sourceIndex];
if (source != null) {
source = this._normalizeSource(source, basicMap);
ignoredSourceSet.add(source);
}
});
}
}
}
exports.default = GoogleIgnoreListConsumer;

View File

@@ -0,0 +1,130 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {BasicSourceMap, IndexMap, MixedSourceMap} from 'metro-source-map';
import {normalizeSourcePath} from 'metro-source-map';
type SourceNameNormalizer = (string, {+sourceRoot?: ?string, ...}) => string;
/**
* Consumes the `x_google_ignoreList` metadata field from a source map and
* exposes various queries on it.
*
* By default, source names are normalized using the same logic that the
* `source-map@0.5.6` package uses internally. This is crucial for keeping the
* sources list in sync with a `SourceMapConsumer` instance.
* If you're using this with a different source map reader (e.g. one that
* doesn't normalize source names at all), you can switch out the normalization
* function in the constructor, e.g.
*
* new GoogleIgnoreListConsumer(map, source => source) // Don't normalize
*/
export default class GoogleIgnoreListConsumer {
constructor(
map: MixedSourceMap,
normalizeSourceFn: SourceNameNormalizer = normalizeSourcePath,
) {
this._sourceMap = map;
this._normalizeSource = normalizeSourceFn;
}
_sourceMap: MixedSourceMap;
_normalizeSource: SourceNameNormalizer;
_ignoredSourceSet: ?Set<string>;
/**
* Returns `true` if the given source is in this map's ignore list, `false`
* otherwise.
*
* When used with the `source-map` package, you'll first use
* `SourceMapConsumer#originalPositionFor` to retrieve a source location,
* then pass that location to `isIgnored`.
*/
isIgnored({source}: {+source: ?string, ...}): boolean {
return source != null && this._getIgnoredSourceSet().has(source);
}
/**
* Returns this map's ignore list as a new array with indices based on
* `sources`.
*
* This array can be used as the `x_google_ignoreList` field of a map whose
* `sources` field is the array that was passed into this method.
*/
toArray(sources: $ReadOnlyArray<?string>): Array<number> {
const ignoredSourceSet = this._getIgnoredSourceSet();
const encoded = [];
for (const [sourceIndex, source] of sources.entries()) {
if (source != null && ignoredSourceSet.has(source)) {
encoded.push(sourceIndex);
}
}
return encoded;
}
/**
* Prepares and caches a set of ignored sources for this map.
*/
_getIgnoredSourceSet(): $ReadOnlySet<string> {
if (!this._ignoredSourceSet) {
const ignoredSourceSet = new Set<string>();
this._buildIgnoredSourceSet(this._sourceMap, ignoredSourceSet);
this._ignoredSourceSet = ignoredSourceSet;
}
return this._ignoredSourceSet;
}
/**
* Collects ignored sources from the given map using the current source name
* normalization function. Handles both index maps (with sections) and plain
* maps.
*
* NOTE: If any sources are repeated in the map, we consider a source to be
* ignored as long as a source with the same normalized name is listed in AT
* LEAST one `x_google_ignoreList` array. Technically, this means we lose
* the granularity afforded by index maps and by the ability to repeat source
* names within a single `sources` array.
*
* Chrome's handling of duplicates is different: only the first occurrence of
* a given source is considered when determining if a source is ignored. It's
* unclear whether this is intentional. Absent a formal spec for
* `x_google_ignoreList`, we will diverge from Chrome for now.
*
* See: https://github.com/ChromeDevTools/devtools-frontend/blob/7afc9157b8d05de06e273284119e9c55a4eadb72/front_end/core/sdk/SourceMap.ts#L425-L429
*/
_buildIgnoredSourceSet(
map: MixedSourceMap,
ignoredSourceSet: Set<string>,
): void {
// eslint-disable-next-line lint/strictly-null
if (map.mappings === undefined) {
const indexMap: IndexMap = map;
indexMap.sections.forEach(section =>
this._buildIgnoredSourceSet(section.map, ignoredSourceSet),
);
return;
}
if ('x_google_ignoreList' in map) {
const basicMap: BasicSourceMap = map;
(basicMap.x_google_ignoreList || []).forEach(sourceIndex => {
let source = basicMap.sources[sourceIndex];
if (source != null) {
source = this._normalizeSource(source, basicMap);
ignoredSourceSet.add(source);
}
});
}
}
}

View File

@@ -0,0 +1,157 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _metroSourceMap = require("metro-source-map");
var vlq = _interopRequireWildcard(require("vlq"));
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
const METADATA_FIELD_FUNCTIONS = 0;
class SourceMetadataMapConsumer {
constructor(map, normalizeSourceFn = _metroSourceMap.normalizeSourcePath) {
this._sourceMap = map;
this._decodedFunctionMapCache = new Map();
this._normalizeSource = normalizeSourceFn;
}
functionNameFor({ line, column, source }) {
if (source && line != null && column != null) {
const mappings = this._getFunctionMappings(source);
if (mappings) {
const mapping = findEnclosingMapping(mappings, {
line,
column,
});
if (mapping) {
return mapping.name;
}
}
}
return null;
}
toArray(sources) {
const metadataBySource = this._getMetadataBySource();
const encoded = [];
for (const source of sources) {
encoded.push(metadataBySource[source] || null);
}
return encoded;
}
_getMetadataBySource() {
if (!this._metadataBySource) {
this._metadataBySource = this._getMetadataObjectsBySourceNames(
this._sourceMap,
);
}
return this._metadataBySource;
}
_getFunctionMappings(source) {
if (this._decodedFunctionMapCache.has(source)) {
return this._decodedFunctionMapCache.get(source);
}
let parsedFunctionMap = null;
const metadataBySource = this._getMetadataBySource();
if (Object.prototype.hasOwnProperty.call(metadataBySource, source)) {
const metadata = metadataBySource[source] || [];
parsedFunctionMap = decodeFunctionMap(metadata[METADATA_FIELD_FUNCTIONS]);
}
this._decodedFunctionMapCache.set(source, parsedFunctionMap);
return parsedFunctionMap;
}
_getMetadataObjectsBySourceNames(map) {
if (map.mappings === undefined) {
const indexMap = map;
return Object.assign(
{},
...indexMap.sections.map((section) =>
this._getMetadataObjectsBySourceNames(section.map),
),
);
}
if ("x_facebook_sources" in map) {
const basicMap = map;
return (basicMap.x_facebook_sources || []).reduce(
(acc, metadata, index) => {
let source = basicMap.sources[index];
if (source != null) {
source = this._normalizeSource(source, basicMap);
acc[source] = metadata;
}
return acc;
},
{},
);
}
return {};
}
}
exports.default = SourceMetadataMapConsumer;
function decodeFunctionMap(functionMap) {
if (!functionMap) {
return [];
}
const parsed = [];
let line = 1;
let nameIndex = 0;
for (const lineMappings of functionMap.mappings.split(";")) {
let column = 0;
for (const mapping of lineMappings.split(",")) {
const [columnDelta, nameDelta, lineDelta = 0] = vlq.decode(mapping);
line += lineDelta;
nameIndex += nameDelta;
column += columnDelta;
parsed.push({
line,
column,
name: functionMap.names[nameIndex],
});
}
}
return parsed;
}
function findEnclosingMapping(mappings, target) {
let first = 0;
let it = 0;
let count = mappings.length;
let step;
while (count > 0) {
it = first;
step = Math.floor(count / 2);
it += step;
if (comparePositions(target, mappings[it]) >= 0) {
first = ++it;
count -= step + 1;
} else {
count = step;
}
}
return first ? mappings[first - 1] : null;
}
function comparePositions(a, b) {
if (a.line === b.line) {
return a.column - b.column;
}
return a.line - b.line;
}

View File

@@ -0,0 +1,230 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
* @oncall react_native
*/
import type {
BasicSourceMap,
FBSourceFunctionMap,
FBSourceMetadata,
FBSourcesArray,
IndexMap,
MixedSourceMap,
} from 'metro-source-map';
import {normalizeSourcePath} from 'metro-source-map';
import * as vlq from 'vlq';
const METADATA_FIELD_FUNCTIONS = 0;
type Position = {
+line: number,
+column: number,
...
};
type FunctionMapping = {
+line: number,
+column: number,
+name: string,
...
};
type SourceNameNormalizer = (string, {+sourceRoot?: ?string, ...}) => string;
type MetadataMap = {[source: string]: ?FBSourceMetadata, ...};
/**
* Consumes the `x_facebook_sources` metadata field from a source map and
* exposes various queries on it.
*
* By default, source names are normalized using the same logic that the
* `source-map@0.5.6` package uses internally. This is crucial for keeping the
* sources list in sync with a `SourceMapConsumer` instance.
* If you're using this with a different source map reader (e.g. one that
* doesn't normalize source names at all), you can switch out the normalization
* function in the constructor, e.g.
*
* new SourceMetadataMapConsumer(map, source => source) // Don't normalize
*/
export default class SourceMetadataMapConsumer {
constructor(
map: MixedSourceMap,
normalizeSourceFn: SourceNameNormalizer = normalizeSourcePath,
) {
this._sourceMap = map;
this._decodedFunctionMapCache = new Map();
this._normalizeSource = normalizeSourceFn;
}
_sourceMap: MixedSourceMap;
_decodedFunctionMapCache: Map<string, ?$ReadOnlyArray<FunctionMapping>>;
_normalizeSource: SourceNameNormalizer;
_metadataBySource: ?MetadataMap;
/**
* Retrieves a human-readable name for the function enclosing a particular
* source location.
*
* When used with the `source-map` package, you'll first use
* `SourceMapConsumer#originalPositionFor` to retrieve a source location,
* then pass that location to `functionNameFor`.
*/
functionNameFor({
line,
column,
source,
}: Position & {+source: ?string, ...}): ?string {
if (source && line != null && column != null) {
const mappings = this._getFunctionMappings(source);
if (mappings) {
const mapping = findEnclosingMapping(mappings, {line, column});
if (mapping) {
return mapping.name;
}
}
}
return null;
}
/**
* Returns this map's source metadata as a new array with the same order as
* `sources`.
*
* This array can be used as the `x_facebook_sources` field of a map whose
* `sources` field is the array that was passed into this method.
*/
toArray(sources: $ReadOnlyArray<string>): FBSourcesArray {
const metadataBySource = this._getMetadataBySource();
const encoded = [];
for (const source of sources) {
encoded.push(metadataBySource[source] || null);
}
return encoded;
}
/**
* Prepares and caches a lookup table of metadata by source name.
*/
_getMetadataBySource(): MetadataMap {
if (!this._metadataBySource) {
this._metadataBySource = this._getMetadataObjectsBySourceNames(
this._sourceMap,
);
}
return this._metadataBySource;
}
/**
* Decodes the function name mappings for the given source if needed, and
* retrieves a sorted, searchable array of mappings.
*/
_getFunctionMappings(source: string): ?$ReadOnlyArray<FunctionMapping> {
if (this._decodedFunctionMapCache.has(source)) {
return this._decodedFunctionMapCache.get(source);
}
let parsedFunctionMap = null;
const metadataBySource = this._getMetadataBySource();
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
if (Object.prototype.hasOwnProperty.call(metadataBySource, source)) {
const metadata = metadataBySource[source] || [];
parsedFunctionMap = decodeFunctionMap(metadata[METADATA_FIELD_FUNCTIONS]);
}
this._decodedFunctionMapCache.set(source, parsedFunctionMap);
return parsedFunctionMap;
}
/**
* Collects source metadata from the given map using the current source name
* normalization function. Handles both index maps (with sections) and plain
* maps.
*
* NOTE: If any sources are repeated in the map (which shouldn't happen in
* Metro, but is technically possible because of index maps) we only keep the
* metadata from the last occurrence of any given source.
*/
_getMetadataObjectsBySourceNames(map: MixedSourceMap): MetadataMap {
// eslint-disable-next-line lint/strictly-null
if (map.mappings === undefined) {
const indexMap: IndexMap = map;
// $FlowFixMe[unsafe-object-assign]
return Object.assign(
{},
...indexMap.sections.map(section =>
this._getMetadataObjectsBySourceNames(section.map),
),
);
}
if ('x_facebook_sources' in map) {
const basicMap: BasicSourceMap = map;
return (basicMap.x_facebook_sources || []).reduce<{
[string]: ?FBSourceMetadata,
}>((acc, metadata, index) => {
let source = basicMap.sources[index];
if (source != null) {
source = this._normalizeSource(source, basicMap);
acc[source] = metadata;
}
return acc;
}, {});
}
return {};
}
}
function decodeFunctionMap(
functionMap: ?FBSourceFunctionMap,
): $ReadOnlyArray<FunctionMapping> {
if (!functionMap) {
return [];
}
const parsed = [];
let line = 1;
let nameIndex = 0;
for (const lineMappings of functionMap.mappings.split(';')) {
let column = 0;
for (const mapping of lineMappings.split(',')) {
const [columnDelta, nameDelta, lineDelta = 0] = vlq.decode(mapping);
line += lineDelta;
nameIndex += nameDelta;
column += columnDelta;
parsed.push({line, column, name: functionMap.names[nameIndex]});
}
}
return parsed;
}
function findEnclosingMapping(
mappings: $ReadOnlyArray<FunctionMapping>,
target: Position,
): ?FunctionMapping {
let first = 0;
let it = 0;
let count = mappings.length;
let step;
while (count > 0) {
it = first;
step = Math.floor(count / 2);
it += step;
if (comparePositions(target, mappings[it]) >= 0) {
first = ++it;
count -= step + 1;
} else {
count = step;
}
}
return first ? mappings[first - 1] : null;
}
function comparePositions(a: Position, b: Position): number {
if (a.line === b.line) {
return a.column - b.column;
}
return a.line - b.line;
}

586
node_modules/metro-symbolicate/src/Symbolication.js generated vendored Normal file
View File

@@ -0,0 +1,586 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
Object.defineProperty(exports, "SourceMetadataMapConsumer", {
enumerable: true,
get: function () {
return _SourceMetadataMapConsumer.default;
},
});
exports.createContext = createContext;
exports.getOriginalPositionFor = getOriginalPositionFor;
exports.parseFileName = parseSingleMapFileName;
exports.symbolicate = symbolicate;
exports.symbolicateAttribution = symbolicateAttribution;
exports.symbolicateChromeTrace = symbolicateChromeTrace;
exports.symbolicateProfilerMap = symbolicateProfilerMap;
exports.unstable_createDirectoryContext = unstable_createDirectoryContext;
var _ChromeHeapSnapshot = require("./ChromeHeapSnapshot");
var _GoogleIgnoreListConsumer = _interopRequireDefault(
require("./GoogleIgnoreListConsumer"),
);
var _SourceMetadataMapConsumer = _interopRequireDefault(
require("./SourceMetadataMapConsumer"),
);
var _fs = _interopRequireDefault(require("fs"));
var _invariant = _interopRequireDefault(require("invariant"));
var _nullthrows = _interopRequireDefault(require("nullthrows"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const UNKNOWN_MODULE_IDS = {
segmentId: 0,
localId: undefined,
};
class SymbolicationContext {
constructor(options) {
this.options = {
inputLineStart: 1,
inputColumnStart: 0,
outputLineStart: 1,
outputColumnStart: 0,
nameSource: "function_names",
};
if (options) {
for (const option of [
"inputLineStart",
"inputColumnStart",
"outputLineStart",
"outputColumnStart",
]) {
if (options[option] != null) {
this.options[option] = options[option];
}
}
if (options.nameSource != null) {
this.options.nameSource = options.nameSource;
}
}
}
symbolicate(stackTrace) {
return stackTrace.replace(
/(?:([^@: \n(]+)(@|:))?(?:(?:([^@: \n(]+):)?(\d+):(\d+)|\[native code\])/g,
(match, func, delimiter, fileName, line, column) => {
if (delimiter === ":" && func && !fileName) {
fileName = func;
func = null;
}
const original = this.getOriginalPositionFor(
line,
column,
this.parseFileName(fileName || ""),
);
return (
(original.source ?? "null") +
":" +
(original.line ?? "null") +
":" +
(original.name ?? "null")
);
},
);
}
symbolicateProfilerMap(mapFile) {
return _fs.default
.readFileSync(mapFile, "utf8")
.split("\n")
.slice(0, -1)
.map((line) => {
const line_list = line.split(" ");
const trampoline = line_list[0];
const js_name = line_list[1];
const offset = parseInt(line_list[2], 10);
if (!offset) {
return trampoline + " " + trampoline;
}
const original = this.getOriginalPositionFor(
this.options.inputLineStart,
offset,
);
return (
trampoline +
" " +
(original.name || js_name) +
"::" +
[original.source, original.line, original.column].join(":")
);
})
.join("\n");
}
symbolicateAttribution(obj) {
const loc = obj.location;
const line = loc.line != null ? loc.line : this.options.inputLineStart;
let column = Number(loc.column != null ? loc.column : loc.virtualOffset);
const file = loc.filename ? this.parseFileName(loc.filename) : null;
let original = this.getOriginalPositionFor(line, column, file);
const isBytecodeRange =
loc.bytecodeSize != null &&
loc.virtualOffset != null &&
loc.column == null;
const virtualOffset = Number(loc.virtualOffset);
const bytecodeSize = Number(loc.bytecodeSize);
while (
isBytecodeRange &&
original.source == null &&
++column < virtualOffset + bytecodeSize
) {
original = this.getOriginalPositionFor(line, column, file);
}
obj.location = {
file: original.source,
line: original.line,
column: original.column,
};
}
symbolicateChromeTrace(traceFile, { stdout, stderr }) {
const content = JSON.parse(_fs.default.readFileSync(traceFile, "utf8"));
if (content.stackFrames == null) {
throw new Error("Unable to locate `stackFrames` section in trace.");
}
const keys = Object.keys(content.stackFrames);
stdout.write("Processing " + keys.length + " frames\n");
keys.forEach((key) => {
const entry = content.stackFrames[key];
let line;
let column;
let funcLine;
let funcColumn;
if (entry.funcVirtAddr != null && entry.offset != null) {
const funcVirtAddr = parseInt(entry.funcVirtAddr, 10);
const offsetInFunction = parseInt(entry.offset, 10);
line = this.options.inputLineStart;
column = funcVirtAddr + offsetInFunction;
funcLine = this.options.inputLineStart;
funcColumn = funcVirtAddr;
} else if (entry.line != null && entry.column != null) {
line = entry.line;
column = entry.column;
funcLine = entry.funcLine;
funcColumn = entry.funcColumn;
} else {
return;
}
const addressOriginal = this.getOriginalPositionDetailsFor(line, column);
let frameName;
if (addressOriginal.functionName) {
frameName = addressOriginal.functionName;
} else {
frameName = entry.name;
if (funcLine != null && funcColumn != null) {
const funcOriginal = this.getOriginalPositionFor(
funcLine,
funcColumn,
);
if (funcOriginal.name != null) {
frameName = funcOriginal.name;
}
} else {
(stderr || stdout).write(
"Warning: no function prolog line/column info; name may be wrong\n",
);
}
}
entry.name = [
frameName,
"(",
[
addressOriginal.source ?? "null",
addressOriginal.line ?? "null",
addressOriginal.column ?? "null",
].join(":"),
")",
].join("");
});
stdout.write("Writing to " + traceFile + "\n");
_fs.default.writeFileSync(traceFile, JSON.stringify(content));
}
getOriginalPositionFor(lineNumber, columnNumber, moduleIds) {
const position = this.getOriginalPositionDetailsFor(
lineNumber,
columnNumber,
moduleIds,
);
return {
line: position.line,
column: position.column,
source: position.source,
name: position.functionName ? position.functionName : position.name,
};
}
symbolicateHermesMinidumpTrace(crashInfo) {
throw new Error("Not implemented");
}
symbolicateHeapSnapshot(snapshotContents) {
const snapshotData =
typeof snapshotContents === "string"
? JSON.parse(snapshotContents)
: snapshotContents;
const processor = new _ChromeHeapSnapshot.ChromeHeapSnapshotProcessor(
snapshotData,
);
for (const frame of processor.traceFunctionInfos()) {
const moduleIds = this.parseFileName(frame.getString("script_name"));
const generatedLine = frame.getNumber("line");
const generatedColumn = frame.getNumber("column");
if (generatedLine === 0 && generatedColumn === 0) {
continue;
}
const {
line: originalLine,
column: originalColumn,
source: originalSource,
functionName: originalFunctionName,
} = this.getOriginalPositionDetailsFor(
frame.getNumber("line") - 1 + this.options.inputLineStart,
frame.getNumber("column") - 1 + this.options.inputColumnStart,
moduleIds,
);
if (originalSource != null) {
frame.setString("script_name", originalSource);
if (originalLine != null) {
frame.setNumber(
"line",
originalLine - this.options.outputLineStart + 1,
);
} else {
frame.setNumber("line", 0);
}
if (originalColumn != null) {
frame.setNumber(
"column",
originalColumn - this.options.outputColumnStart + 1,
);
} else {
frame.setNumber("column", 0);
}
}
frame.setString("name", originalFunctionName ?? frame.getString("name"));
}
return snapshotData;
}
symbolicateHermesCoverageTrace(coverageInfo) {
const symbolicatedTrace = [];
const { executedFunctions } = coverageInfo;
if (executedFunctions != null) {
for (const stackItem of executedFunctions) {
const { line, column, SourceURL } = stackItem;
const generatedLine = line + this.options.inputLineStart;
const generatedColumn = column + this.options.inputColumnStart;
const originalPosition = this.getOriginalPositionDetailsFor(
generatedLine,
generatedColumn,
this.parseFileName(SourceURL || ""),
);
symbolicatedTrace.push(originalPosition);
}
}
return symbolicatedTrace;
}
getOriginalPositionDetailsFor(lineNumber, columnNumber, moduleIds) {
throw new Error("Not implemented");
}
parseFileName(str) {
throw new Error("Not implemented");
}
}
class SingleMapSymbolicationContext extends SymbolicationContext {
constructor(SourceMapConsumer, sourceMapContent, options = {}) {
super(options);
this._SourceMapConsumer = SourceMapConsumer;
const sourceMapJson =
typeof sourceMapContent === "string"
? JSON.parse(sourceMapContent.replace(/^\)\]\}'/, ""))
: sourceMapContent;
const segments = {
0: this._initSegment(sourceMapJson),
};
if (sourceMapJson.x_facebook_segments) {
for (const key of Object.keys(sourceMapJson.x_facebook_segments)) {
const map = sourceMapJson.x_facebook_segments[key];
segments[key] = this._initSegment(map);
}
}
this._legacyFormat =
sourceMapJson.x_facebook_segments != null ||
sourceMapJson.x_facebook_offsets != null;
this._segments = segments;
}
_initSegment(map) {
const useFunctionNames = this.options.nameSource === "function_names";
const { _SourceMapConsumer: SourceMapConsumer } = this;
return {
get consumer() {
Object.defineProperty(this, "consumer", {
value: new SourceMapConsumer(map),
});
return this.consumer;
},
moduleOffsets: map.x_facebook_offsets || [],
get sourceFunctionsConsumer() {
Object.defineProperty(this, "sourceFunctionsConsumer", {
value: useFunctionNames
? new _SourceMetadataMapConsumer.default(map)
: null,
});
return this.sourceFunctionsConsumer;
},
get googleIgnoreListConsumer() {
Object.defineProperty(this, "googleIgnoreListConsumer", {
value: new _GoogleIgnoreListConsumer.default(map),
});
return this.googleIgnoreListConsumer;
},
hermesOffsets: map.x_hermes_function_offsets,
};
}
symbolicateHermesMinidumpTrace(crashInfo) {
const symbolicatedTrace = [];
const { callstack } = crashInfo;
if (callstack != null) {
for (const stackItem of callstack) {
if (stackItem.NativeCode) {
symbolicatedTrace.push(stackItem);
} else {
const {
CJSModuleOffset,
SegmentID,
SourceURL,
FunctionID,
ByteCodeOffset: localOffset,
} = stackItem;
const cjsModuleOffsetOrSegmentID = (0, _nullthrows.default)(
CJSModuleOffset ?? SegmentID,
"Either CJSModuleOffset or SegmentID must be specified in the Hermes stack frame",
);
const moduleInformation = this.parseFileName(SourceURL);
const generatedLine =
cjsModuleOffsetOrSegmentID + this.options.inputLineStart;
const segment =
this._segments[moduleInformation.segmentId.toString()];
const hermesOffsets = segment?.hermesOffsets;
if (!hermesOffsets) {
symbolicatedTrace.push({
line: null,
column: null,
source: null,
functionName: null,
name: null,
isIgnored: false,
});
} else {
const segmentOffsets =
hermesOffsets[Number(cjsModuleOffsetOrSegmentID)];
const generatedColumn =
segmentOffsets[FunctionID] +
localOffset +
this.options.inputColumnStart;
const originalPosition = this.getOriginalPositionDetailsFor(
generatedLine,
generatedColumn,
moduleInformation,
);
symbolicatedTrace.push(originalPosition);
}
}
}
}
return symbolicatedTrace;
}
symbolicateHermesCoverageTrace(coverageInfo) {
const symbolicatedTrace = [];
const { executedFunctions } = coverageInfo;
if (executedFunctions != null) {
for (const stackItem of executedFunctions) {
const { line, column, SourceURL } = stackItem;
const generatedLine = line + this.options.inputLineStart;
const generatedColumn = column + this.options.inputColumnStart;
const originalPosition = this.getOriginalPositionDetailsFor(
generatedLine,
generatedColumn,
this.parseFileName(SourceURL || ""),
);
symbolicatedTrace.push(originalPosition);
}
}
return symbolicatedTrace;
}
getOriginalPositionDetailsFor(lineNumber, columnNumber, moduleIds) {
lineNumber =
lineNumber != null
? lineNumber - this.options.inputLineStart + 1
: lineNumber;
columnNumber =
columnNumber != null
? columnNumber - this.options.inputColumnStart + 0
: columnNumber;
if (!moduleIds) {
moduleIds = UNKNOWN_MODULE_IDS;
}
let moduleLineOffset = 0;
const metadata = this._segments[moduleIds.segmentId + ""];
const { localId } = moduleIds;
if (localId != null) {
const { moduleOffsets } = metadata;
if (!moduleOffsets) {
throw new Error(
"Module ID given for a source map that does not have " +
"an x_facebook_offsets field",
);
}
if (moduleOffsets[localId] == null) {
throw new Error("Unknown module ID: " + localId);
}
moduleLineOffset = moduleOffsets[localId];
}
const original = metadata.consumer.originalPositionFor({
line: Number(lineNumber) + moduleLineOffset,
column: Number(columnNumber),
});
if (metadata.sourceFunctionsConsumer) {
original.functionName =
metadata.sourceFunctionsConsumer.functionNameFor(original) || null;
} else {
original.functionName = null;
}
original.isIgnored = metadata.googleIgnoreListConsumer.isIgnored(original);
return {
...original,
line:
original.line != null
? original.line - 1 + this.options.outputLineStart
: original.line,
column:
original.column != null
? original.column - 0 + this.options.outputColumnStart
: original.column,
};
}
parseFileName(str) {
if (this._legacyFormat) {
return parseSingleMapFileName(str);
}
return UNKNOWN_MODULE_IDS;
}
}
class DirectorySymbolicationContext extends SymbolicationContext {
constructor(SourceMapConsumer, rootDir, options = {}) {
super(options);
this._fileMaps = new Map();
this._rootDir = rootDir;
this._SourceMapConsumer = SourceMapConsumer;
}
_loadMap(mapFilename) {
(0, _invariant.default)(
_fs.default.existsSync(mapFilename),
`Could not read source map from '${mapFilename}'`,
);
let fileMap = this._fileMaps.get(mapFilename);
if (fileMap == null) {
fileMap = new SingleMapSymbolicationContext(
this._SourceMapConsumer,
_fs.default.readFileSync(mapFilename, "utf8"),
this.options,
);
this._fileMaps.set(mapFilename, fileMap);
}
return fileMap;
}
getOriginalPositionDetailsFor(lineNumber, columnNumber, filename) {
(0, _invariant.default)(
filename != null,
"filename is required for DirectorySymbolicationContext",
);
let mapFilename;
const relativeFilename = _path.default.relative(
this._rootDir,
_path.default.resolve(this._rootDir, filename),
);
if (!relativeFilename.startsWith("..")) {
mapFilename = _path.default.join(
this._rootDir,
relativeFilename + ".map",
);
}
if (mapFilename == null || !_fs.default.existsSync(mapFilename)) {
lineNumber =
lineNumber != null
? lineNumber -
this.options.inputLineStart +
this.options.outputLineStart
: lineNumber;
columnNumber =
columnNumber != null
? columnNumber -
this.options.inputColumnStart +
this.options.outputColumnStart
: columnNumber;
return {
line: lineNumber,
column: columnNumber,
source: filename,
name: null,
functionName: null,
isIgnored: false,
};
}
return this._loadMap(mapFilename).getOriginalPositionDetailsFor(
lineNumber,
columnNumber,
);
}
parseFileName(str) {
return str;
}
}
function parseSingleMapFileName(str) {
const modMatch = str.match(/^(\d+).js$/);
if (modMatch != null) {
return {
segmentId: 0,
localId: Number(modMatch[1]),
};
}
const segMatch = str.match(/^seg-(\d+)(?:_(\d+))?.js$/);
if (segMatch != null) {
return {
segmentId: Number(segMatch[1]),
localId: segMatch[2] ? Number(segMatch[2]) : null,
};
}
return UNKNOWN_MODULE_IDS;
}
function createContext(SourceMapConsumer, sourceMapContent, options = {}) {
return new SingleMapSymbolicationContext(
SourceMapConsumer,
sourceMapContent,
options,
);
}
function unstable_createDirectoryContext(
SourceMapConsumer,
rootDir,
options = {},
) {
return new DirectorySymbolicationContext(SourceMapConsumer, rootDir, options);
}
function getOriginalPositionFor(lineNumber, columnNumber, moduleIds, context) {
return context.getOriginalPositionFor(lineNumber, columnNumber, moduleIds);
}
function symbolicate(stackTrace, context) {
return context.symbolicate(stackTrace);
}
function symbolicateProfilerMap(mapFile, context) {
return context.symbolicateProfilerMap(mapFile);
}
function symbolicateAttribution(obj, context) {
context.symbolicateAttribution(obj);
}
function symbolicateChromeTrace(traceFile, { stdout, stderr }, context) {
return context.symbolicateChromeTrace(traceFile, {
stdout,
stderr,
});
}

View File

@@ -0,0 +1,927 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
* @oncall react_native
*/
import type {ChromeHeapSnapshot} from './ChromeHeapSnapshot';
import type {HermesFunctionOffsets, MixedSourceMap} from 'metro-source-map';
import {ChromeHeapSnapshotProcessor} from './ChromeHeapSnapshot';
import GoogleIgnoreListConsumer from './GoogleIgnoreListConsumer';
import SourceMetadataMapConsumer from './SourceMetadataMapConsumer';
import fs from 'fs';
import invariant from 'invariant';
import nullthrows from 'nullthrows';
import path from 'path';
// flowlint-next-line untyped-type-import:off
import {typeof SourceMapConsumer} from 'source-map';
type SingleMapModuleIds = {
segmentId: number,
localId: ?number,
...
};
type ContextOptionsInput = {
+nameSource?: 'function_names' | 'identifier_names',
+inputLineStart?: number,
+inputColumnStart?: number,
+outputLineStart?: number,
+outputColumnStart?: number,
...
};
type SizeAttributionMap = {
location: {
file: ?string,
filename?: string,
bytecodeSize?: number,
virtualOffset?: number,
line: ?number,
column: ?number,
},
...
};
type ChromeTraceEntry = {
column: number,
funcColumn: number,
funcLine: number,
funcVirtAddr: number,
line: number,
name: string,
offset: number,
};
type ChromeTrace = {
stackFrames: {[string]: ChromeTraceEntry},
};
type HermesMinidumpCrashInfo = {
+callstack: $ReadOnlyArray<HermesMinidumpStackFrame | NativeCodeStackFrame>,
...
};
type HermesMinidumpStackFrame = $ReadOnly<{
ByteCodeOffset: number,
FunctionID: number,
// NOTE: CJSModuleOffset has been renamed to SegmentID. Support both formats for now.
CJSModuleOffset?: number,
SegmentID?: number,
SourceURL: string,
StackFrameRegOffs: string,
SourceLocation?: string,
}>;
type HermesCoverageInfo = {
+executedFunctions: $ReadOnlyArray<HermesCoverageStackFrame>,
};
type HermesCoverageStackFrame = $ReadOnly<{
line: number, // SegmentID or zero-based line,
column: number, // VirtualOffset or zero-based column,
SourceURL: ?string,
}>;
type NativeCodeStackFrame = $ReadOnly<{
NativeCode: true,
StackFrameRegOffs: string,
}>;
type SymbolicatedStackTrace = $ReadOnlyArray<
SymbolicatedStackFrame | NativeCodeStackFrame,
>;
type SymbolicatedStackFrame = $ReadOnly<{
line: ?number,
column: ?number,
source: ?string,
functionName: ?string,
name: ?string,
isIgnored: boolean,
}>;
const UNKNOWN_MODULE_IDS: SingleMapModuleIds = {
segmentId: 0,
localId: undefined,
};
class SymbolicationContext<ModuleIdsT> {
+options: {
+nameSource: 'function_names' | 'identifier_names',
+inputLineStart: number,
+inputColumnStart: number,
+outputLineStart: number,
+outputColumnStart: number,
...
};
constructor(options: ContextOptionsInput) {
this.options = {
inputLineStart: 1,
inputColumnStart: 0,
outputLineStart: 1,
outputColumnStart: 0,
nameSource: 'function_names',
};
if (options) {
for (const option of [
'inputLineStart',
'inputColumnStart',
'outputLineStart',
'outputColumnStart',
] as const) {
if (options[option] != null) {
/* $FlowFixMe[incompatible-type] error found during natural inference
* roll-out. See https://fburl.com/workplace/tc9m3tcf */
this.options[option] = options[option];
}
}
if (options.nameSource != null) {
// $FlowFixMe[cannot-write]
this.options.nameSource = options.nameSource;
}
}
}
// parse stack trace with String.replace
// replace the matched part of stack trace to symbolicated result
// sample stack trace:
// IOS: foo@4:18131, Android: bar:4:18063
// sample stack trace with module id:
// IOS: foo@123.js:4:18131, Android: bar:123.js:4:18063
// sample stack trace without function name:
// 123.js:4:18131
// sample result:
// IOS: foo.js:57:foo, Android: bar.js:75:bar
symbolicate(stackTrace: string): string {
return stackTrace.replace(
/(?:([^@: \n(]+)(@|:))?(?:(?:([^@: \n(]+):)?(\d+):(\d+)|\[native code\])/g,
(match, func, delimiter, fileName, line, column) => {
if (delimiter === ':' && func && !fileName) {
fileName = func;
func = null;
}
const original = this.getOriginalPositionFor(
line,
column,
this.parseFileName(fileName || ''),
);
return (
(original.source ?? 'null') +
':' +
(original.line ?? 'null') +
':' +
(original.name ?? 'null')
);
},
);
}
// Taking in a map like
// trampoline offset (optional js function name)
// JS_0158_xxxxxxxxxxxxxxxxxxxxxx fe 91081
// JS_0159_xxxxxxxxxxxxxxxxxxxxxx Ft 68651
// JS_0160_xxxxxxxxxxxxxxxxxxxxxx value 50700
// JS_0161_xxxxxxxxxxxxxxxxxxxxxx setGapAtCursor 0
// JS_0162_xxxxxxxxxxxxxxxxxxxxxx (unknown) 50818
// JS_0163_xxxxxxxxxxxxxxxxxxxxxx value 108267
symbolicateProfilerMap(mapFile: string): string {
return fs
.readFileSync(mapFile, 'utf8')
.split('\n')
.slice(0, -1)
.map(line => {
const line_list = line.split(' ');
const trampoline = line_list[0];
const js_name = line_list[1];
const offset = parseInt(line_list[2], 10);
if (!offset) {
return trampoline + ' ' + trampoline;
}
const original = this.getOriginalPositionFor(
this.options.inputLineStart,
offset,
);
return (
trampoline +
' ' +
(original.name || js_name) +
'::' +
[original.source, original.line, original.column].join(':')
);
})
.join('\n');
}
symbolicateAttribution(obj: SizeAttributionMap): void {
const loc = obj.location;
const line = loc.line != null ? loc.line : this.options.inputLineStart;
let column = Number(loc.column != null ? loc.column : loc.virtualOffset);
const file = loc.filename ? this.parseFileName(loc.filename) : null;
let original = this.getOriginalPositionFor(line, column, file);
const isBytecodeRange =
loc.bytecodeSize != null &&
loc.virtualOffset != null &&
loc.column == null;
const virtualOffset = Number(loc.virtualOffset);
const bytecodeSize = Number(loc.bytecodeSize);
// Functions compiled from Metro-bundled modules will often have a little bit
// of unmapped wrapper code right at the beginning - which is where we query.
// Let's attribute them to where the inner module code originates instead.
// This loop is O(n*log(n)) in the size of the function, but we will generally
// either:
// 1. Find a non-null mapping within one or two iterations; or
// 2. Reach the end of the function without encountering mappings - this might
// happen for function bodies that never throw (generally very short).
while (
isBytecodeRange &&
original.source == null &&
++column < virtualOffset + bytecodeSize
) {
original = this.getOriginalPositionFor(line, column, file);
}
obj.location = {
file: original.source,
line: original.line,
column: original.column,
};
}
// Symbolicate chrome trace "stackFrames" section.
// Each frame in it has three fields: name, funcVirtAddr(optional), offset(optional).
// funcVirtAddr and offset are only available if trace is generated from
// hbc bundle without debug info.
symbolicateChromeTrace(
traceFile: string,
{
stdout,
stderr,
}: {
stdout: stream$Writable,
stderr: stream$Writable,
...
},
): void {
const content: ChromeTrace = JSON.parse(fs.readFileSync(traceFile, 'utf8'));
if (content.stackFrames == null) {
throw new Error('Unable to locate `stackFrames` section in trace.');
}
const keys = Object.keys(content.stackFrames);
stdout.write('Processing ' + keys.length + ' frames\n');
keys.forEach(key => {
const entry = content.stackFrames[key];
let line;
let column;
// Function entrypoint line/column; used for symbolicating function name
// with legacy source maps (or when --no-function-names is set).
let funcLine;
let funcColumn;
if (entry.funcVirtAddr != null && entry.offset != null) {
// Without debug information.
const funcVirtAddr = parseInt(entry.funcVirtAddr, 10);
const offsetInFunction = parseInt(entry.offset, 10);
// Main bundle always use hard-coded line value 1.
// TODO: support multiple bundle/module.
line = this.options.inputLineStart;
column = funcVirtAddr + offsetInFunction;
funcLine = this.options.inputLineStart;
funcColumn = funcVirtAddr;
} else if (entry.line != null && entry.column != null) {
// For hbc bundle with debug info, name field may already have source
// information for the bundle; we still can use the Metro
// source map to symbolicate the bundle frame addresses further to its
// original source code.
line = entry.line;
column = entry.column;
funcLine = entry.funcLine;
funcColumn = entry.funcColumn;
} else {
// Native frames.
return;
}
// Symbolicate original file/line/column.
const addressOriginal = this.getOriginalPositionDetailsFor(line, column);
let frameName;
if (addressOriginal.functionName) {
frameName = addressOriginal.functionName;
} else {
frameName = entry.name;
// Symbolicate function name.
if (funcLine != null && funcColumn != null) {
const funcOriginal = this.getOriginalPositionFor(
funcLine,
funcColumn,
);
if (funcOriginal.name != null) {
frameName = funcOriginal.name;
}
} else {
// No function line/column info.
(stderr || stdout).write(
'Warning: no function prolog line/column info; name may be wrong\n',
);
}
}
// Output format is: funcName(file:line:column)
entry.name = [
frameName,
'(',
[
addressOriginal.source ?? 'null',
addressOriginal.line ?? 'null',
addressOriginal.column ?? 'null',
].join(':'),
')',
].join('');
});
stdout.write('Writing to ' + traceFile + '\n');
fs.writeFileSync(traceFile, JSON.stringify(content));
}
/*
* A helper function to return a mapping {line, column} object for a given input
* line and column, and optionally a module ID.
*/
getOriginalPositionFor(
lineNumber: ?number,
columnNumber: ?number,
moduleIds: ?ModuleIdsT,
): {
line: ?number,
column: ?number,
source: ?string,
name: ?string,
} {
const position = this.getOriginalPositionDetailsFor(
lineNumber,
columnNumber,
moduleIds,
);
return {
line: position.line,
column: position.column,
source: position.source,
name: position.functionName ? position.functionName : position.name,
};
}
/*
* Symbolicates the JavaScript stack trace extracted from the minidump
* produced by hermes
*/
symbolicateHermesMinidumpTrace(
crashInfo: HermesMinidumpCrashInfo,
): SymbolicatedStackTrace {
throw new Error('Not implemented');
}
/**
* Symbolicates heap alloction stacks in a Chrome-formatted heap
* snapshot/timeline.
* Line and column offsets in options (both input and output) are _ignored_,
* because this format has a well-defined convention (1-based lines and
* columns).
*/
symbolicateHeapSnapshot(
snapshotContents: string | ChromeHeapSnapshot,
): ChromeHeapSnapshot {
const snapshotData: ChromeHeapSnapshot =
typeof snapshotContents === 'string'
? JSON.parse(snapshotContents)
: snapshotContents;
const processor = new ChromeHeapSnapshotProcessor(snapshotData);
for (const frame of processor.traceFunctionInfos()) {
const moduleIds = this.parseFileName(frame.getString('script_name'));
const generatedLine = frame.getNumber('line');
const generatedColumn = frame.getNumber('column');
if (generatedLine === 0 && generatedColumn === 0) {
continue;
}
const {
line: originalLine,
column: originalColumn,
source: originalSource,
functionName: originalFunctionName,
} = this.getOriginalPositionDetailsFor(
frame.getNumber('line') - 1 + this.options.inputLineStart,
frame.getNumber('column') - 1 + this.options.inputColumnStart,
moduleIds,
);
if (originalSource != null) {
frame.setString('script_name', originalSource);
if (originalLine != null) {
frame.setNumber(
'line',
originalLine - this.options.outputLineStart + 1,
);
} else {
frame.setNumber('line', 0);
}
if (originalColumn != null) {
frame.setNumber(
'column',
originalColumn - this.options.outputColumnStart + 1,
);
} else {
frame.setNumber('column', 0);
}
}
frame.setString('name', originalFunctionName ?? frame.getString('name'));
}
return snapshotData;
}
/*
* Symbolicates the JavaScript stack trace extracted from the coverage information
* produced by HermesRuntime::getExecutedFunctions.
*/
symbolicateHermesCoverageTrace(
coverageInfo: HermesCoverageInfo,
): SymbolicatedStackTrace {
const symbolicatedTrace = [];
const {executedFunctions} = coverageInfo;
if (executedFunctions != null) {
for (const stackItem of executedFunctions) {
const {line, column, SourceURL} = stackItem;
const generatedLine = line + this.options.inputLineStart;
const generatedColumn = column + this.options.inputColumnStart;
const originalPosition = this.getOriginalPositionDetailsFor(
generatedLine,
generatedColumn,
this.parseFileName(SourceURL || ''),
);
symbolicatedTrace.push(originalPosition);
}
}
return symbolicatedTrace;
}
/*
* An internal helper function similar to getOriginalPositionFor. This one
* returns both `name` and `functionName` fields so callers can distinguish the
* source of the name.
*/
getOriginalPositionDetailsFor(
lineNumber: ?number,
columnNumber: ?number,
moduleIds: ?ModuleIdsT,
): SymbolicatedStackFrame {
throw new Error('Not implemented');
}
parseFileName(str: string): ModuleIdsT {
throw new Error('Not implemented');
}
}
class SingleMapSymbolicationContext extends SymbolicationContext<SingleMapModuleIds> {
+_segments: {
+[id: string]: {
// $FlowFixMe[value-as-type]
+consumer: SourceMapConsumer,
+moduleOffsets: $ReadOnlyArray<number>,
+sourceFunctionsConsumer: ?SourceMetadataMapConsumer,
+hermesOffsets: ?HermesFunctionOffsets,
+googleIgnoreListConsumer: GoogleIgnoreListConsumer,
},
...
};
+_legacyFormat: boolean;
// $FlowFixMe[value-as-type]
+_SourceMapConsumer: SourceMapConsumer;
constructor(
// $FlowFixMe[value-as-type]
SourceMapConsumer: SourceMapConsumer,
sourceMapContent: string | MixedSourceMap,
options: ContextOptionsInput = {},
) {
super(options);
this._SourceMapConsumer = SourceMapConsumer;
const sourceMapJson: MixedSourceMap =
typeof sourceMapContent === 'string'
? JSON.parse(sourceMapContent.replace(/^\)\]\}'/, ''))
: sourceMapContent;
const segments = {
'0': this._initSegment(sourceMapJson),
};
if (sourceMapJson.x_facebook_segments) {
for (const key of Object.keys(sourceMapJson.x_facebook_segments)) {
// $FlowFixMe[incompatible-use]
const map = sourceMapJson.x_facebook_segments[key];
// $FlowFixMe[prop-missing]
segments[key] = this._initSegment(map);
}
}
this._legacyFormat =
sourceMapJson.x_facebook_segments != null ||
sourceMapJson.x_facebook_offsets != null;
this._segments = segments;
}
// $FlowFixMe[missing-local-annot]
_initSegment(map: MixedSourceMap) {
const useFunctionNames = this.options.nameSource === 'function_names';
const {_SourceMapConsumer: SourceMapConsumer} = this;
return {
get consumer() {
// $FlowFixMe[object-this-reference]
Object.defineProperty(this, 'consumer', {
value: new SourceMapConsumer(map),
});
// $FlowFixMe[object-this-reference]
return this.consumer;
},
moduleOffsets: map.x_facebook_offsets || [],
get sourceFunctionsConsumer() {
// $FlowFixMe[object-this-reference]
Object.defineProperty(this, 'sourceFunctionsConsumer', {
value: useFunctionNames ? new SourceMetadataMapConsumer(map) : null,
});
// $FlowFixMe[object-this-reference]
return this.sourceFunctionsConsumer;
},
get googleIgnoreListConsumer() {
// $FlowFixMe[object-this-reference]
Object.defineProperty(this, 'googleIgnoreListConsumer', {
value: new GoogleIgnoreListConsumer(map),
});
// $FlowFixMe[object-this-reference]
return this.googleIgnoreListConsumer;
},
hermesOffsets: map.x_hermes_function_offsets,
};
}
symbolicateHermesMinidumpTrace(
crashInfo: HermesMinidumpCrashInfo,
): SymbolicatedStackTrace {
const symbolicatedTrace = [];
const {callstack} = crashInfo;
if (callstack != null) {
for (const stackItem of callstack) {
if (stackItem.NativeCode) {
symbolicatedTrace.push(stackItem);
} else {
const {
CJSModuleOffset,
SegmentID,
SourceURL,
FunctionID,
ByteCodeOffset: localOffset,
} = stackItem;
const cjsModuleOffsetOrSegmentID = nullthrows(
CJSModuleOffset ?? SegmentID,
'Either CJSModuleOffset or SegmentID must be specified in the Hermes stack frame',
);
const moduleInformation = this.parseFileName(SourceURL);
const generatedLine =
cjsModuleOffsetOrSegmentID + this.options.inputLineStart;
const segment =
this._segments[moduleInformation.segmentId.toString()];
const hermesOffsets = segment?.hermesOffsets;
if (!hermesOffsets) {
symbolicatedTrace.push({
line: null,
column: null,
source: null,
functionName: null,
name: null,
isIgnored: false,
});
} else {
const segmentOffsets =
hermesOffsets[Number(cjsModuleOffsetOrSegmentID)];
const generatedColumn =
segmentOffsets[FunctionID] +
localOffset +
this.options.inputColumnStart;
const originalPosition = this.getOriginalPositionDetailsFor(
generatedLine,
generatedColumn,
moduleInformation,
);
symbolicatedTrace.push(originalPosition);
}
}
}
}
return symbolicatedTrace;
}
symbolicateHermesCoverageTrace(
coverageInfo: HermesCoverageInfo,
): SymbolicatedStackTrace {
const symbolicatedTrace = [];
const {executedFunctions} = coverageInfo;
if (executedFunctions != null) {
for (const stackItem of executedFunctions) {
const {line, column, SourceURL} = stackItem;
const generatedLine = line + this.options.inputLineStart;
const generatedColumn = column + this.options.inputColumnStart;
const originalPosition = this.getOriginalPositionDetailsFor(
generatedLine,
generatedColumn,
this.parseFileName(SourceURL || ''),
);
symbolicatedTrace.push(originalPosition);
}
}
return symbolicatedTrace;
}
/*
* An internal helper function similar to getOriginalPositionFor. This one
* returns both `name` and `functionName` fields so callers can distinguish the
* source of the name.
*/
getOriginalPositionDetailsFor(
lineNumber: ?number,
columnNumber: ?number,
moduleIds: ?SingleMapModuleIds,
): SymbolicatedStackFrame {
// Adjust arguments to source-map's input coordinates
lineNumber =
lineNumber != null
? lineNumber - this.options.inputLineStart + 1
: lineNumber;
columnNumber =
columnNumber != null
? columnNumber - this.options.inputColumnStart + 0
: columnNumber;
if (!moduleIds) {
moduleIds = UNKNOWN_MODULE_IDS;
}
let moduleLineOffset = 0;
const metadata = this._segments[moduleIds.segmentId + ''];
const {localId} = moduleIds;
if (localId != null) {
const {moduleOffsets} = metadata;
if (!moduleOffsets) {
throw new Error(
'Module ID given for a source map that does not have ' +
'an x_facebook_offsets field',
);
}
if (moduleOffsets[localId] == null) {
throw new Error('Unknown module ID: ' + localId);
}
moduleLineOffset = moduleOffsets[localId];
}
const original = metadata.consumer.originalPositionFor({
line: Number(lineNumber) + moduleLineOffset,
column: Number(columnNumber),
});
if (metadata.sourceFunctionsConsumer) {
original.functionName =
metadata.sourceFunctionsConsumer.functionNameFor(original) || null;
} else {
original.functionName = null;
}
original.isIgnored = metadata.googleIgnoreListConsumer.isIgnored(original);
return {
...original,
line:
original.line != null
? original.line - 1 + this.options.outputLineStart
: original.line,
column:
original.column != null
? original.column - 0 + this.options.outputColumnStart
: original.column,
};
}
parseFileName(str: string): SingleMapModuleIds {
if (this._legacyFormat) {
return parseSingleMapFileName(str);
}
return UNKNOWN_MODULE_IDS;
}
}
class DirectorySymbolicationContext extends SymbolicationContext<string> {
+_fileMaps: Map<string, SingleMapSymbolicationContext>;
+_rootDir: string;
// $FlowFixMe[value-as-type]
+_SourceMapConsumer: SourceMapConsumer;
constructor(
// $FlowFixMe[value-as-type]
SourceMapConsumer: SourceMapConsumer,
rootDir: string,
options: ContextOptionsInput = {},
) {
super(options);
this._fileMaps = new Map();
this._rootDir = rootDir;
this._SourceMapConsumer = SourceMapConsumer;
}
_loadMap(mapFilename: string): SingleMapSymbolicationContext {
invariant(
fs.existsSync(mapFilename),
`Could not read source map from '${mapFilename}'`,
);
let fileMap = this._fileMaps.get(mapFilename);
if (fileMap == null) {
fileMap = new SingleMapSymbolicationContext(
this._SourceMapConsumer,
fs.readFileSync(mapFilename, 'utf8'),
this.options,
);
this._fileMaps.set(mapFilename, fileMap);
}
return fileMap;
}
/*
* An internal helper function similar to getOriginalPositionFor. This one
* returns both `name` and `functionName` fields so callers can distinguish the
* source of the name.
*/
getOriginalPositionDetailsFor(
lineNumber: ?number,
columnNumber: ?number,
filename: ?string,
): SymbolicatedStackFrame {
invariant(
filename != null,
'filename is required for DirectorySymbolicationContext',
);
let mapFilename;
const relativeFilename = path.relative(
this._rootDir,
path.resolve(this._rootDir, filename),
);
// Lock down access to files outside the root dir.
if (!relativeFilename.startsWith('..')) {
mapFilename = path.join(this._rootDir, relativeFilename + '.map');
}
if (mapFilename == null || !fs.existsSync(mapFilename)) {
// Adjust arguments to the output coordinates
lineNumber =
lineNumber != null
? lineNumber -
this.options.inputLineStart +
this.options.outputLineStart
: lineNumber;
columnNumber =
columnNumber != null
? columnNumber -
this.options.inputColumnStart +
this.options.outputColumnStart
: columnNumber;
return {
line: lineNumber,
column: columnNumber,
source: filename,
name: null,
functionName: null,
isIgnored: false,
};
}
return this._loadMap(mapFilename).getOriginalPositionDetailsFor(
lineNumber,
columnNumber,
);
}
parseFileName(str: string): string {
return str;
}
}
/*
* If the file name of a stack frame is numeric (+ ".js"), we assume it's a
* lazily injected module coming from a "random access bundle". We are using
* special source maps for these bundles, so that we can symbolicate stack
* traces for multiple injected files with a single source map.
*
* There is also a convention for callsites that are in split segments of a
* bundle, named either `seg-3.js` for segment #3 for example, or `seg-3_5.js`
* for module #5 of segment #3 of a segmented RAM bundle.
*/
function parseSingleMapFileName(str: string): SingleMapModuleIds {
const modMatch = str.match(/^(\d+).js$/);
if (modMatch != null) {
return {segmentId: 0, localId: Number(modMatch[1])};
}
const segMatch = str.match(/^seg-(\d+)(?:_(\d+))?.js$/);
if (segMatch != null) {
return {
segmentId: Number(segMatch[1]),
localId: segMatch[2] ? Number(segMatch[2]) : null,
};
}
return UNKNOWN_MODULE_IDS;
}
function createContext(
// $FlowFixMe[value-as-type]
SourceMapConsumer: SourceMapConsumer,
sourceMapContent: string | MixedSourceMap,
options: ContextOptionsInput = {},
): SingleMapSymbolicationContext {
return new SingleMapSymbolicationContext(
SourceMapConsumer,
sourceMapContent,
options,
);
}
function unstable_createDirectoryContext(
// $FlowFixMe[value-as-type]
SourceMapConsumer: SourceMapConsumer,
rootDir: string,
options: ContextOptionsInput = {},
): DirectorySymbolicationContext {
return new DirectorySymbolicationContext(SourceMapConsumer, rootDir, options);
}
function getOriginalPositionFor<ModuleIdsT>(
lineNumber: ?number,
columnNumber: ?number,
moduleIds: ?ModuleIdsT,
context: SymbolicationContext<ModuleIdsT>,
): {
line: ?number,
column: ?number,
source: ?string,
name: ?string,
} {
return context.getOriginalPositionFor(lineNumber, columnNumber, moduleIds);
}
function symbolicate<ModuleIdsT>(
stackTrace: string,
context: SymbolicationContext<ModuleIdsT>,
): string {
return context.symbolicate(stackTrace);
}
function symbolicateProfilerMap<ModuleIdsT>(
mapFile: string,
context: SymbolicationContext<ModuleIdsT>,
): string {
return context.symbolicateProfilerMap(mapFile);
}
function symbolicateAttribution<ModuleIdsT>(
obj: SizeAttributionMap,
context: SymbolicationContext<ModuleIdsT>,
): void {
context.symbolicateAttribution(obj);
}
function symbolicateChromeTrace<ModuleIdsT>(
traceFile: string,
{
stdout,
stderr,
}: {
stdout: stream$Writable,
stderr: stream$Writable,
...
},
context: SymbolicationContext<ModuleIdsT>,
): void {
return context.symbolicateChromeTrace(traceFile, {stdout, stderr});
}
export {
createContext,
unstable_createDirectoryContext,
getOriginalPositionFor,
parseSingleMapFileName as parseFileName,
symbolicate,
symbolicateProfilerMap,
symbolicateAttribution,
symbolicateChromeTrace,
SourceMetadataMapConsumer,
};

8
node_modules/metro-symbolicate/src/index.js generated vendored Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env node
"use strict";
var _symbolicate = _interopRequireDefault(require("./symbolicate"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
(0, _symbolicate.default)().then((code) => process.exit(code));

16
node_modules/metro-symbolicate/src/index.js.flow generated vendored Normal file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env node
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import symbolicate from './symbolicate';
// $FlowFixMe[unused-promise]
symbolicate().then(code => process.exit(code));

240
node_modules/metro-symbolicate/src/symbolicate.js generated vendored Normal file
View File

@@ -0,0 +1,240 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = main;
var Symbolication = _interopRequireWildcard(require("./Symbolication"));
var _fs = _interopRequireDefault(require("fs"));
var _sourceMap = require("source-map");
var _stream = require("stream");
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
function printHelp() {
const usages = [
"Usage: " + __filename + " <source-map-file>",
" " + __filename + " <source-map-file> <line> [column]",
" " + __filename + " <source-map-file> <moduleId>.js <line> [column]",
" " + __filename + " <source-map-file> <mapfile>.profmap",
" " +
__filename +
" <source-map-file> --attribution < in.jsonl > out.jsonl",
" " + __filename + " <source-map-file> <tracefile>.cpuprofile",
" Optional flags:",
" --no-function-names",
" --hermes-crash (mutually exclusive with --hermes-coverage)",
" --hermes-coverage (mutually exclusive with --hermes-crash)",
" --input-line-start <line> (default: 1)",
" --input-column-start <column> (default: 0)",
" --output-line-start <line> (default: 1)",
" --output-column-start <column> (default: 0)",
];
console.error(usages.join("\n"));
}
async function main(
argvInput = process.argv.slice(2),
{ stdin, stderr, stdout } = process,
) {
const argv = argvInput.slice();
function checkAndRemoveArg(arg, valuesPerArg = 0) {
let values = null;
for (let idx = argv.indexOf(arg); idx !== -1; idx = argv.indexOf(arg)) {
argv.splice(idx, 1);
values = values || [];
values.push(argv.splice(idx, valuesPerArg));
}
return values;
}
function checkAndRemoveArgWithValue(arg) {
const values = checkAndRemoveArg(arg, 1);
return values ? values[0][0] : null;
}
try {
const noFunctionNames = checkAndRemoveArg("--no-function-names");
const isHermesCrash = checkAndRemoveArg("--hermes-crash");
const isCoverage = checkAndRemoveArg("--hermes-coverage");
const inputLineStart = Number.parseInt(
checkAndRemoveArgWithValue("--input-line-start") || "1",
10,
);
const inputColumnStart = Number.parseInt(
checkAndRemoveArgWithValue("--input-column-start") || "0",
10,
);
const outputLineStart = Number.parseInt(
checkAndRemoveArgWithValue("--output-line-start") || "1",
10,
);
const outputColumnStart = Number.parseInt(
checkAndRemoveArgWithValue("--output-column-start") || "0",
10,
);
if (argv.length < 1 || argv.length > 4) {
printHelp();
return 1;
}
if (isHermesCrash && isCoverage) {
console.error(
"Pass either --hermes-crash or --hermes-coverage, not both",
);
printHelp();
return 1;
}
const sourceMapFileName = argv.shift();
const options = {
nameSource: noFunctionNames ? "identifier_names" : "function_names",
inputLineStart,
inputColumnStart,
outputLineStart,
outputColumnStart,
};
let context;
if (_fs.default.lstatSync(sourceMapFileName).isDirectory()) {
context = Symbolication.unstable_createDirectoryContext(
_sourceMap.SourceMapConsumer,
sourceMapFileName,
options,
);
} else {
const content = _fs.default.readFileSync(sourceMapFileName, "utf8");
context = Symbolication.createContext(
_sourceMap.SourceMapConsumer,
content,
options,
);
}
if (argv.length === 0) {
const stackTrace = await readAll(stdin);
if (isHermesCrash) {
const stackTraceJSON = JSON.parse(stackTrace);
const symbolicatedTrace =
context.symbolicateHermesMinidumpTrace(stackTraceJSON);
stdout.write(JSON.stringify(symbolicatedTrace));
} else if (isCoverage) {
const stackTraceJSON = JSON.parse(stackTrace);
const symbolicatedTrace =
context.symbolicateHermesCoverageTrace(stackTraceJSON);
stdout.write(JSON.stringify(symbolicatedTrace));
} else {
stdout.write(context.symbolicate(stackTrace));
}
} else if (argv[0].endsWith(".profmap")) {
stdout.write(context.symbolicateProfilerMap(argv[0]));
} else if (
argv[0].endsWith(".heapsnapshot") ||
argv[0].endsWith(".heaptimeline")
) {
stdout.write(
JSON.stringify(
context.symbolicateHeapSnapshot(
_fs.default.readFileSync(argv[0], "utf8"),
),
),
);
} else if (argv[0] === "--attribution") {
let lineBuffer = "";
const streamToLines = new _stream.Transform({
transform(data, _enc, callback) {
lineBuffer += data.toString();
const lines = lineBuffer.split("\n");
for (let i = 0, e = lines.length - 1; i < e; i++) {
streamToLines.push(lines[i]);
}
lineBuffer = lines[lines.length - 1];
callback();
},
});
const symbolicateLines = new _stream.Transform({
transform(data, enc, callback) {
const obj = JSON.parse(data.toString());
context.symbolicateAttribution(obj);
symbolicateLines.push(JSON.stringify(obj) + "\n");
callback();
},
objectMode: true,
});
await waitForStream(
stdin.pipe(streamToLines).pipe(symbolicateLines).pipe(stdout),
);
} else if (argv[0].endsWith(".cpuprofile")) {
context.symbolicateChromeTrace(argv[0], {
stdout,
stderr,
});
} else {
let moduleIds;
if (argv[0].endsWith(".js")) {
moduleIds = context.parseFileName(argv[0]);
argv.shift();
} else {
moduleIds = null;
}
const lineNumber = argv.shift();
const columnNumber = argv.shift() || 0;
const original = context.getOriginalPositionFor(
+lineNumber,
+columnNumber,
moduleIds,
);
stdout.write(
[
original.source ?? "null",
original.line ?? "null",
original.name ?? "null",
].join(":") + "\n",
);
}
} catch (error) {
stderr.write(error + "\n");
return 1;
}
return 0;
}
function readAll(stream) {
return new Promise((resolve) => {
let data = "";
if (stream.isTTY === true) {
resolve(data);
return;
}
stream.setEncoding("utf8");
stream.on("readable", () => {
let chunk;
while ((chunk = stream.read())) {
data += chunk.toString();
}
});
stream.on("end", () => {
resolve(data);
});
});
}
function waitForStream(stream) {
return new Promise((resolve) => {
stream.on("finish", resolve);
});
}

259
node_modules/metro-symbolicate/src/symbolicate.js.flow generated vendored Normal file
View File

@@ -0,0 +1,259 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
// Symbolicates a JavaScript stack trace using a source map.
// In our first form, we read a stack trace from stdin and symbolicate it via
// the provided source map.
// In our second form, we symbolicate using an explicit line number, and
// optionally a column.
// In our third form, we symbolicate using a module ID, a line number, and
// optionally a column.
import * as Symbolication from './Symbolication';
import fs from 'fs';
// $FlowFixMe[untyped-import] source-map
import {SourceMapConsumer} from 'source-map';
import {Transform} from 'stream';
function printHelp() {
const usages = [
'Usage: ' + __filename + ' <source-map-file>',
' ' + __filename + ' <source-map-file> <line> [column]',
' ' + __filename + ' <source-map-file> <moduleId>.js <line> [column]',
' ' + __filename + ' <source-map-file> <mapfile>.profmap',
' ' +
__filename +
' <source-map-file> --attribution < in.jsonl > out.jsonl',
' ' + __filename + ' <source-map-file> <tracefile>.cpuprofile',
' Optional flags:',
' --no-function-names',
' --hermes-crash (mutually exclusive with --hermes-coverage)',
' --hermes-coverage (mutually exclusive with --hermes-crash)',
' --input-line-start <line> (default: 1)',
' --input-column-start <column> (default: 0)',
' --output-line-start <line> (default: 1)',
' --output-column-start <column> (default: 0)',
];
console.error(usages.join('\n'));
}
export default async function main(
argvInput: Array<string> = process.argv.slice(2),
{
stdin,
stderr,
stdout,
}: $ReadOnly<{
stdin: stream$Readable | tty$ReadStream,
stderr: stream$Writable,
stdout: stream$Writable,
...
}> = (process: $FlowFixMe),
): Promise<number> {
const argv = argvInput.slice();
function checkAndRemoveArg(arg: string, valuesPerArg: number = 0) {
let values: null | Array<Array<string>> = null;
for (let idx = argv.indexOf(arg); idx !== -1; idx = argv.indexOf(arg)) {
argv.splice(idx, 1);
values = values || [];
values.push(argv.splice(idx, valuesPerArg));
}
return values;
}
function checkAndRemoveArgWithValue(arg: string) {
const values = checkAndRemoveArg(arg, 1);
return values ? values[0][0] : null;
}
try {
const noFunctionNames = checkAndRemoveArg('--no-function-names');
const isHermesCrash = checkAndRemoveArg('--hermes-crash');
const isCoverage = checkAndRemoveArg('--hermes-coverage');
const inputLineStart = Number.parseInt(
checkAndRemoveArgWithValue('--input-line-start') || '1',
10,
);
const inputColumnStart = Number.parseInt(
checkAndRemoveArgWithValue('--input-column-start') || '0',
10,
);
const outputLineStart = Number.parseInt(
checkAndRemoveArgWithValue('--output-line-start') || '1',
10,
);
const outputColumnStart = Number.parseInt(
checkAndRemoveArgWithValue('--output-column-start') || '0',
10,
);
if (argv.length < 1 || argv.length > 4) {
/* eslint no-path-concat: "off" */
printHelp();
return 1;
}
if (isHermesCrash && isCoverage) {
console.error(
'Pass either --hermes-crash or --hermes-coverage, not both',
);
printHelp();
return 1;
}
// Read the source map.
const sourceMapFileName = argv.shift();
const options = {
nameSource: noFunctionNames ? 'identifier_names' : 'function_names',
inputLineStart,
inputColumnStart,
outputLineStart,
outputColumnStart,
};
let context;
// $FlowFixMe[incompatible-type]
if (fs.lstatSync(sourceMapFileName).isDirectory()) {
context = Symbolication.unstable_createDirectoryContext(
SourceMapConsumer,
// $FlowFixMe[incompatible-type]
sourceMapFileName,
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
options,
);
} else {
// $FlowFixMe[incompatible-type]
const content = fs.readFileSync(sourceMapFileName, 'utf8');
context = Symbolication.createContext(
SourceMapConsumer,
content,
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
options,
);
}
if (argv.length === 0) {
const stackTrace = await readAll(stdin);
if (isHermesCrash) {
const stackTraceJSON = JSON.parse(stackTrace);
const symbolicatedTrace =
context.symbolicateHermesMinidumpTrace(stackTraceJSON);
stdout.write(JSON.stringify(symbolicatedTrace));
} else if (isCoverage) {
const stackTraceJSON = JSON.parse(stackTrace);
const symbolicatedTrace =
context.symbolicateHermesCoverageTrace(stackTraceJSON);
stdout.write(JSON.stringify(symbolicatedTrace));
} else {
stdout.write(context.symbolicate(stackTrace));
}
} else if (argv[0].endsWith('.profmap')) {
stdout.write(context.symbolicateProfilerMap(argv[0]));
} else if (
argv[0].endsWith('.heapsnapshot') ||
argv[0].endsWith('.heaptimeline')
) {
stdout.write(
JSON.stringify(
context.symbolicateHeapSnapshot(fs.readFileSync(argv[0], 'utf8')),
),
);
} else if (argv[0] === '--attribution') {
let lineBuffer = '';
const streamToLines: Transform = new Transform({
transform(data, _enc, callback) {
// Take arbitrary strings, output single lines
lineBuffer += data.toString();
const lines = lineBuffer.split('\n');
for (let i = 0, e = lines.length - 1; i < e; i++) {
streamToLines.push(lines[i]);
}
lineBuffer = lines[lines.length - 1];
callback();
},
});
const symbolicateLines: Transform = new Transform({
transform(data, enc, callback) {
// This is JSONL, so each line is a separate JSON object
const obj = JSON.parse(data.toString());
context.symbolicateAttribution(obj);
symbolicateLines.push(JSON.stringify(obj) + '\n');
callback();
},
objectMode: true,
});
await waitForStream(
stdin.pipe(streamToLines).pipe(symbolicateLines).pipe(stdout),
);
} else if (argv[0].endsWith('.cpuprofile')) {
// NOTE: synchronous
context.symbolicateChromeTrace(argv[0], {stdout, stderr});
} else {
// read-from-argv form.
let moduleIds;
if (argv[0].endsWith('.js')) {
moduleIds = context.parseFileName(argv[0]);
argv.shift();
} else {
moduleIds = null;
}
const lineNumber = argv.shift();
const columnNumber = argv.shift() || 0;
const original = context.getOriginalPositionFor(
+lineNumber,
+columnNumber,
// $FlowFixMe[incompatible-type] context is a union here and so this parameter is a union
moduleIds,
);
stdout.write(
[
original.source ?? 'null',
original.line ?? 'null',
original.name ?? 'null',
].join(':') + '\n',
);
}
} catch (error) {
stderr.write(error + '\n');
return 1;
}
return 0;
}
function readAll(stream: stream$Readable | tty$ReadStream) {
return new Promise<string>(resolve => {
let data = '';
if (stream.isTTY === true) {
resolve(data);
return;
}
stream.setEncoding('utf8');
stream.on('readable', () => {
let chunk;
// flowlint-next-line sketchy-null-string:off
while ((chunk = stream.read())) {
data += chunk.toString();
}
});
stream.on('end', () => {
resolve(data);
});
});
}
function waitForStream(stream: $FlowFixMe) {
return new Promise(resolve => {
stream.on('finish', resolve);
});
}