diff --git a/src/profile-logic/symbolication.ts b/src/profile-logic/symbolication.ts index 3186fb7b3a..cdc01ac0d2 100644 --- a/src/profile-logic/symbolication.ts +++ b/src/profile-logic/symbolication.ts @@ -789,8 +789,8 @@ function _partiallyApplySymbolicationStep( if (funcIndex === undefined) { // Need a new func. funcIndex = funcTable.length; - funcTable.isJS[funcIndex] = false; - funcTable.relevantForJS[funcIndex] = false; + funcTable.isJS[funcIndex] = funcTable.isJS[oldFunc]; + funcTable.relevantForJS[funcIndex] = funcTable.relevantForJS[oldFunc]; funcTable.resource[funcIndex] = resourceIndex; funcTable.source[funcIndex] = null; funcTable.lineNumber[funcIndex] = null; diff --git a/src/test/fixtures/example-symbol-table.ts b/src/test/fixtures/example-symbol-table.ts index 509aa8d79b..40af714732 100644 --- a/src/test/fixtures/example-symbol-table.ts +++ b/src/test/fixtures/example-symbol-table.ts @@ -280,3 +280,45 @@ export const partialSymbolTable: ExampleSymbolTable = { export const completeSymbolTableAsTuple = completeSymbolTable.asTuple; export const partialSymbolTableAsTuple = partialSymbolTable.asTuple; + +// A symbol table for a JIT dump file (e.g. jit-52344.dump), simulating JS +// functions symbolicated from samply + jitdump. The outer function +// "renderButton.js" has an inlined call to "useState.js" at address 0x000a. +const jitDumpSyms = [ + { + address: 0, + name: 'renderButton.js', + file: 'Button.tsx', + lineRanges: [ + { + startAddress: 0x0, + line: 42, + }, + { + startAddress: 0x8, + line: 45, + inlinedCall: { + name: 'useState.js', + file: 'react.js', + lineRanges: [ + { + startAddress: 0x8, + line: 100, + }, + ], + }, + }, + ], + }, + { + address: 0x2000, + name: 'runJobs.js', + file: 'scheduler.js', + }, +]; + +export const jitDumpSymbolTable: ExampleSymbolTable = { + symbols: jitDumpSyms, + asTuple: _makeSymbolTableAsTuple(jitDumpSyms), + getAddressResult: _makeGetAddressResultFunction(jitDumpSyms), +}; diff --git a/src/test/store/symbolication.test.ts b/src/test/store/symbolication.test.ts index 372bdd5980..40c47f71ed 100644 --- a/src/test/store/symbolication.test.ts +++ b/src/test/store/symbolication.test.ts @@ -7,6 +7,7 @@ import { getProfileFromTextSamples } from '../fixtures/profiles/processed-profil import { completeSymbolTable, partialSymbolTable, + jitDumpSymbolTable, } from '../fixtures/example-symbol-table'; import type { ExampleSymbolTable } from '../fixtures/example-symbol-table'; import type { MarkerPayload } from 'firefox-profiler/types'; @@ -29,6 +30,7 @@ import { doSymbolicateProfile } from '../../actions/receive-profile'; import { changeSelectedCallNode, changeExpandedCallNodes, + changeImplementationFilter, } from '../../actions/profile-view'; import { formatTree, formatStack } from '../fixtures/utils'; import { assertSetContainsOnly } from '../fixtures/custom-assertions'; @@ -44,18 +46,17 @@ import { SymbolsNotFoundError } from '../../profile-logic/errors'; */ describe('doSymbolicateProfile', function () { // Initialize a store, an unsymbolicated profile, and helper functions. - function init() { + function init(profile = _createUnsymbolicatedProfile()) { // The rejection in `requestSymbolsFromServer` outputs an error log, let's // silence it here. The fact that we call it is tested in // symbol-store.test.js. jest.spyOn(console, 'log').mockImplementation(() => {}); - const profile = _createUnsymbolicatedProfile(); const store = storeWithProfile(profile); - let symbolTable = completeSymbolTable; + let firefoxSymbolTable = completeSymbolTable; function switchSymbolTable(otherSymbolTable: ExampleSymbolTable) { - symbolTable = otherSymbolTable; + firefoxSymbolTable = otherSymbolTable; } let symbolicationProviderMode: 'from-server' | 'from-browser' = 'from-browser'; @@ -67,12 +68,19 @@ describe('doSymbolicateProfile', function () { requestSymbolsFromServer: async (requests: LibSymbolicationRequest[]) => requests.map((request) => { const { lib, addresses } = request; - if (lib.debugName !== 'firefox.pdb') { + + const symbolTables: Partial> = { + 'firefox.pdb': firefoxSymbolTable, + 'jit-52344.dump': jitDumpSymbolTable, + }; + + const symbolTable = symbolTables[lib.debugName]; + if (symbolTable === undefined) { return { type: 'ERROR' as const, request, error: new SymbolsNotFoundError( - 'Should only have lib called firefox.pdb', + `Lib name ${lib.debugName} is not in the list of known names: ${Object.keys(symbolTables).join(', ')}`, lib ), }; @@ -123,7 +131,7 @@ describe('doSymbolicateProfile', function () { } return readSymbolsFromSymbolTable( addresses, - symbolTable.asTuple, + firefoxSymbolTable.asTuple, (s: string) => s ); }, @@ -478,6 +486,28 @@ describe('doSymbolicateProfile', function () { ]); }); + it('inline frames for JS functions appear in the JS-only call tree after symbolication', async () => { + const { + store: { dispatch, getState }, + profile, + symbolStore, + switchSymbolProviderMode, + } = init(_createUnsymbolicatedJitProfile()); + + switchSymbolProviderMode('from-server'); + + await doSymbolicateProfile(dispatch, profile, symbolStore); + + // Check that the `useState.js` node shows up in the JS-only call tree. + // This function is an inline frame from the jitdump symbol info. + dispatch(changeImplementationFilter('js')); + expect(formatTree(getCallTree(getState()))).toEqual([ + '- renderButton.js (total: 1, self: —)', + ' - useState.js (total: 1, self: 1)', + '- runJobs.js (total: 1, self: 1)', + ]); + }); + it('can re-symbolicate a partially-symbolicated profile even if it needs to add funcs to the funcTable', async () => { const { store: { dispatch, getState }, @@ -623,3 +653,11 @@ function _createUnsymbolicatedProfile() { return profile; } + +function _createUnsymbolicatedJitProfile() { + // See jitDumpSyms (in example-symbol-table.ts) for the corresponding symbols. + const { profile } = getProfileFromTextSamples(` + renderButton.js[lib:jit-52344.dump][address:a] runJobs.js[lib:jit-52344.dump][address:2000] + `); + return profile; +}