Files
compiler-explorer/lib/parsers/asm-parser.ts
Ofek bcb397f56b Add noUnusedLocals to tsconfig + fix the new alerts (#6396)
<!-- THIS COMMENT IS INVISIBLE IN THE FINAL PR, BUT FEEL FREE TO REMOVE
IT
Thanks for taking the time to improve CE. We really appreciate it.
Before opening the PR, please make sure that the tests & linter pass
their checks,
  by running `make check`.
In the best case scenario, you are also adding tests to back up your
changes,
  but don't sweat it if you don't. We can discuss them at a later date.
Feel free to append your name to the CONTRIBUTORS.md file
Thanks again, we really appreciate this!
-->
2024-05-14 18:46:22 +03:00

835 lines
32 KiB
TypeScript

// Copyright (c) 2015, Compiler Explorer Authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
import _ from 'underscore';
import {isString} from '../../shared/common-utils.js';
import {
AsmResultLabel,
AsmResultSource,
ParsedAsmResult,
ParsedAsmResultLine,
} from '../../types/asmresult/asmresult.interfaces.js';
import {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
import {assert} from '../assert.js';
import {PropertyGetter} from '../properties.interfaces.js';
import * as utils from '../utils.js';
import {IAsmParser} from './asm-parser.interfaces.js';
import {AsmRegex} from './asmregex.js';
export type ParsingContext = {
files: Record<number, string>;
source: AsmResultSource | undefined | null;
dontMaskFilenames: boolean;
prevLabel: string;
prevLabelIsUserFunction: boolean;
};
export class AsmParser extends AsmRegex implements IAsmParser {
labelFindNonMips: RegExp;
labelFindMips: RegExp;
mipsLabelDefinition: RegExp;
dataDefn: RegExp;
fileFind: RegExp;
hasOpcodeRe: RegExp;
instructionRe: RegExp;
identifierFindRe: RegExp;
hasNvccOpcodeRe: RegExp;
definesFunction: RegExp;
definesGlobal: RegExp;
definesWeak: RegExp;
indentedLabelDef: RegExp;
assignmentDef: RegExp;
directive: RegExp;
startAppBlock: RegExp;
endAppBlock: RegExp;
startAsmNesting: RegExp;
endAsmNesting: RegExp;
cudaBeginDef: RegExp;
cudaEndDef: RegExp;
binaryHideFuncRe: RegExp | null;
maxAsmLines: number;
asmOpcodeRe: RegExp;
relocationRe: RegExp;
relocDataSymNameRe: RegExp;
lineRe: RegExp;
labelRe: RegExp;
destRe: RegExp;
commentRe: RegExp;
instOpcodeRe: RegExp;
commentOnly: RegExp;
commentOnlyNvcc: RegExp;
sourceTag: RegExp;
sourceD2Tag: RegExp;
sourceCVTag: RegExp;
source6502Dbg: RegExp;
source6502DbgEnd: RegExp;
sourceStab: RegExp;
stdInLooking: RegExp;
endBlock: RegExp;
blockComments: RegExp;
constructor(compilerProps?: PropertyGetter) {
super();
this.labelFindNonMips = /[.A-Z_a-z][\w$.]*/g;
// MIPS labels can start with a $ sign, but other assemblers use $ to mean literal.
this.labelFindMips = /[$.A-Z_a-z][\w$.]*/g;
this.mipsLabelDefinition = /^\$[\w$.]+:/;
this.dataDefn =
/^\s*\.(ascii|asciz|[1248]?byte|dc(?:\.[abdlswx])?|dcb(?:\.[bdlswx])?|ds(?:\.[bdlpswx])?|double|dword|fill|float|half|hword|int|long|octa|quad|short|single|skip|space|string(?:8|16|32|64)?|value|word|xword|zero)/;
this.fileFind = /^\s*\.(?:cv_)?file\s+(\d+)\s+"([^"]+)"(\s+"([^"]+)")?.*/;
// Opcode expression here matches LLVM-style opcodes of the form `%blah = opcode`
this.hasOpcodeRe = /^\s*(%[$.A-Z_a-z][\w$.]*\s*=\s*)?[A-Za-z]/;
this.instructionRe = /^\s*[A-Za-z]+/;
this.identifierFindRe = /[$.@A-Z_a-z]\w*/g;
this.hasNvccOpcodeRe = /^\s*[@A-Za-z|]/;
this.definesFunction = /^\s*\.(type.*,\s*[#%@]function|proc\s+[.A-Z_a-z][\w$.]*:.*)$/;
this.definesGlobal = /^\s*\.(?:globa?l|GLB|export)\s*([.A-Z_a-z][\w$.]*)/;
this.definesWeak = /^\s*\.(?:weakext|weak)\s*([.A-Z_a-z][\w$.]*)/;
this.indentedLabelDef = /^\s*([$.A-Z_a-z][\w$.]*):/;
this.assignmentDef = /^\s*([$.A-Z_a-z][\w$.]*)\s*=/;
this.directive = /^\s*\..*$/;
// These four regexes when phrased as /\s*#APP.*/ etc exhibit costly polynomial backtracking
// Instead use ^$ and test with regex.test(line.trim()), more robust anyway
this.startAppBlock = /^#APP.*$/;
this.endAppBlock = /^#NO_APP.*$/;
this.startAsmNesting = /^# Begin ASM.*$/;
this.endAsmNesting = /^# End ASM.*$/;
this.cudaBeginDef = /\.(entry|func)\s+(?:\([^)]*\)\s*)?([$.A-Z_a-z][\w$.]*)\($/;
this.cudaEndDef = /^\s*\)\s*$/;
this.binaryHideFuncRe = null;
this.maxAsmLines = 5000;
if (compilerProps) {
const binaryHideFuncReValue = compilerProps('binaryHideFuncRe');
if (binaryHideFuncReValue) {
assert(isString(binaryHideFuncReValue));
this.binaryHideFuncRe = new RegExp(binaryHideFuncReValue);
}
this.maxAsmLines = compilerProps('maxLinesOfAsm', this.maxAsmLines);
}
this.asmOpcodeRe = /^\s*(?<address>[\da-f]+):\s*(?<opcodes>([\da-f]{2} ?)+)\s*(?<disasm>.*)/;
this.relocationRe = /^\s*(?<address>[\da-f]+):\s*(?<relocname>(R_[\dA-Z_]+))\s*(?<relocdata>.*)/;
this.relocDataSymNameRe = /^(?<symname>[^\d-+][\w.]*)?\s*(?<addend_or_value>.*)$/;
if (process.platform === 'win32') {
this.lineRe = /^([A-Z]:\/[^:]+):(?<line>\d+).*/;
} else {
this.lineRe = /^(\/[^:]+):(?<line>\d+).*/;
}
// labelRe is made very greedy as it's also used with demangled objdump
// output (eg. it can have c++ template with <>).
this.labelRe = /^([\da-f]+)\s+<(.+)>:$/;
this.destRe = /\s([\da-f]+)\s+<([^+>]+)(\+0x[\da-f]+)?>$/;
this.commentRe = /[#;]/;
this.instOpcodeRe = /(\.inst\.?\w?)\s*(.*)/;
// Lines matching the following pattern are considered comments:
// - starts with '#', '@', '//' or a single ';' (non repeated)
// - starts with ';;' and the first non-whitespace before end of line is not #
this.commentOnly = /^\s*(((#|@|\/\/).*)|(\/\*.*\*\/)|(;\s*)|(;[^;].*)|(;;\s*[^\s#].*))$/;
this.commentOnlyNvcc = /^\s*(((#|;|\/\/).*)|(\/\*.*\*\/))$/;
this.sourceTag = /^\s*\.loc\s+(\d+)\s+(\d+)\s+(.*)/;
this.sourceD2Tag = /^\s*\.d2line\s+(\d+),?\s*(\d*).*/;
this.sourceCVTag = /^\s*\.cv_loc\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+).*/;
this.source6502Dbg = /^\s*\.dbg\s+line,\s*"([^"]+)",\s*(\d+)/;
this.source6502DbgEnd = /^\s*\.dbg\s+line[^,]/;
this.sourceStab = /^\s*\.stabn\s+(\d+),0,(\d+),.*/;
this.stdInLooking = /<stdin>|^-$|example\.[^/]+$|<source>/;
this.endBlock = /\.(cfi_endproc|data|text|section)/;
this.blockComments = /^[\t ]*\/\*(\*(?!\/)|[^*])*\*\/\s*/gm;
}
checkVLIWpacket(line, inVLIWpacket) {
return inVLIWpacket;
}
hasOpcode(line, inNvccCode?, inVLIWpacket?) {
// Remove any leading label definition...
const match = line.match(this.labelDef);
if (match) {
line = line.substring(match[0].length);
}
// Strip any comments
line = line.split(this.commentRe, 1)[0];
// .inst generates an opcode, so also counts
if (this.instOpcodeRe.test(line)) return true;
// Detect assignment, that's not an opcode...
if (this.assignmentDef.test(line)) return false;
if (inNvccCode) {
return !!this.hasNvccOpcodeRe.test(line);
}
return !!this.hasOpcodeRe.test(line);
}
labelFindFor(asmLines) {
const isMips = _.any(asmLines, line => !!this.mipsLabelDefinition.test(line));
return isMips ? this.labelFindMips : this.labelFindNonMips;
}
findUsedLabels(asmLines, filterDirectives) {
const labelsUsed = {};
const weakUsages = {};
const labelFind = this.labelFindFor(asmLines);
// The current label set is the set of labels all pointing at the current code, so:
// foo:
// bar:
// add r0, r0, #1
// in this case [foo, bar] would be the label set for the add instruction.
let currentLabelSet: string[] = [];
let inLabelGroup = false;
let inCustomAssembly = 0;
const startBlock = /\.cfi_startproc/;
const endBlock = /\.cfi_endproc/;
let inFunction = false;
let inNvccCode = false;
let inVLIWpacket = false;
// Scan through looking for definite label usages (ones used by opcodes),
// and ones that are weakly used: that is, their use is conditional on another label.
// For example:
// .foo: .string "moo"
// .baz: .quad .foo
// mov eax, .baz
// In this case, the '.baz' is used by an opcode, and so is strongly used.
// The '.foo' is weakly used by .baz.
// Also, if we have random data definitions within a block of a function (between
// cfi_startproc and cfi_endproc), we assume they are strong usages. This covers things
// like jump tables embedded in ARM code.
// See https://github.com/compiler-explorer/compiler-explorer/issues/2788
for (let line of asmLines) {
if (this.startAppBlock.test(line.trim()) || this.startAsmNesting.test(line.trim())) {
inCustomAssembly++;
} else if (this.endAppBlock.test(line.trim()) || this.endAsmNesting.test(line.trim())) {
inCustomAssembly--;
} else if (startBlock.test(line)) {
inFunction = true;
} else if (endBlock.test(line)) {
inFunction = false;
} else if (this.cudaBeginDef.test(line)) {
inNvccCode = true;
} else {
inVLIWpacket = this.checkVLIWpacket(line, inVLIWpacket);
}
if (inCustomAssembly > 0) line = this.fixLabelIndentation(line);
let match = line.match(this.labelDef);
if (match) {
if (inLabelGroup) currentLabelSet.push(match[1]);
else currentLabelSet = [match[1]];
inLabelGroup = true;
} else {
inLabelGroup = false;
}
match = line.match(this.definesGlobal);
if (!match) match = line.match(this.definesWeak);
if (!match) match = line.match(this.cudaBeginDef);
if (match) {
labelsUsed[match[1]] = true;
}
const definesFunction = line.match(this.definesFunction);
if (!definesFunction && (!line || line[0] === '.')) continue;
match = line.match(labelFind);
if (!match) continue;
if (!filterDirectives || this.hasOpcode(line, inNvccCode, inVLIWpacket) || definesFunction) {
// Only count a label as used if it's used by an opcode, or else we're not filtering directives.
for (const label of match) labelsUsed[label] = true;
} else {
// If we have a current label, then any subsequent opcode or data definition's labels are referred to
// weakly by that label.
const isDataDefinition = !!this.dataDefn.test(line);
const isOpcode = this.hasOpcode(line, inNvccCode, inVLIWpacket);
if (isDataDefinition || isOpcode) {
for (const currentLabel of currentLabelSet) {
if (inFunction && isDataDefinition) {
// Data definitions in the middle of code should be treated as if they were used strongly.
for (const label of match) labelsUsed[label] = true;
} else {
if (!weakUsages[currentLabel]) weakUsages[currentLabel] = [];
for (const label of match) weakUsages[currentLabel].push(label);
}
}
}
}
}
// Now follow the chains of used labels, marking any weak references they refer
// to as also used. We iteratively do this until either no new labels are found,
// or we hit a limit (only here to prevent a pathological case from hanging).
function markUsed(label) {
labelsUsed[label] = true;
}
const MaxLabelIterations = 10;
for (let iter = 0; iter < MaxLabelIterations; ++iter) {
const toAdd: string[] = [];
_.each(labelsUsed, (t, label) => {
// jshint ignore:line
_.each(weakUsages[label], (nowused: string) => {
if (labelsUsed[nowused]) return;
toAdd.push(nowused);
});
});
if (!toAdd) break;
_.each(toAdd, markUsed);
}
return labelsUsed;
}
parseFiles(asmLines) {
const files = {};
for (const line of asmLines) {
const match = line.match(this.fileFind);
if (match) {
const lineNum = parseInt(match[1]);
if (match[4] && !line.includes('.cv_file')) {
// Clang-style file directive '.file X "dir" "filename"'
if (match[4].startsWith('/')) {
files[lineNum] = match[4];
} else {
files[lineNum] = match[2] + '/' + match[4];
}
} else {
files[lineNum] = match[2];
}
}
}
return files;
}
// Remove labels which do not have a definition.
removeLabelsWithoutDefinition(asm, labelDefinitions) {
for (const obj of asm) {
if (obj.labels) {
obj.labels = obj.labels.filter(label => labelDefinitions[label.name]);
}
}
}
// Get labels which are used in the given line.
getUsedLabelsInLine(line) {
const labelsInLine: AsmResultLabel[] = [];
// Strip any comments
const instruction = line.split(this.commentRe, 1)[0];
// Remove the instruction.
const params = instruction.replace(this.instructionRe, '');
const removedCol = instruction.length - params.length + 1;
params.replace(this.identifierFindRe, (label, index) => {
const startCol = removedCol + index;
labelsInLine.push({
name: label,
range: {
startCol: startCol,
endCol: startCol + label.length,
},
});
});
return labelsInLine;
}
protected isUserFunctionByLookingAhead(context: ParsingContext, asmLines: string[], idxFrom: number): boolean {
const funcContext: ParsingContext = {
files: context.files,
source: undefined,
dontMaskFilenames: true,
prevLabelIsUserFunction: false,
prevLabel: '',
};
for (let idx = idxFrom; idx < asmLines.length; idx++) {
const line = asmLines[idx];
const endprocMatch = line.match(this.endBlock);
if (endprocMatch) return false;
this.handleSource(funcContext, line);
this.handleStabs(funcContext, line);
this.handle6502(funcContext, line);
if (funcContext.source?.mainsource) return true;
}
return false;
}
protected handleSource(context: ParsingContext, line: string) {
let match = line.match(this.sourceTag);
if (match) {
const file = utils.maskRootdir(context.files[parseInt(match[1])]);
const sourceLine = parseInt(match[2]);
if (file) {
if (context.dontMaskFilenames) {
context.source = {
file: file,
line: sourceLine,
mainsource: !!this.stdInLooking.test(file),
};
} else {
context.source = {
file: this.stdInLooking.test(file) ? null : file,
line: sourceLine,
};
}
const sourceCol = parseInt(match[3]);
if (!isNaN(sourceCol) && sourceCol !== 0) {
context.source.column = sourceCol;
}
} else {
context.source = null;
}
} else {
match = line.match(this.sourceD2Tag);
if (match) {
const sourceLine = parseInt(match[1]);
context.source = {
file: null,
line: sourceLine,
};
} else {
match = line.match(this.sourceCVTag);
if (match) {
// cv_loc reports: function file line column
const sourceLine = parseInt(match[3]);
const file = utils.maskRootdir(context.files[parseInt(match[2])]);
if (context.dontMaskFilenames) {
context.source = {
file: file,
line: sourceLine,
mainsource: !!this.stdInLooking.test(file),
};
} else {
context.source = {
file: this.stdInLooking.test(file) ? null : file,
line: sourceLine,
};
}
const sourceCol = parseInt(match[4]);
if (!isNaN(sourceCol) && sourceCol !== 0) {
context.source.column = sourceCol;
}
}
}
}
}
protected handleStabs(context: ParsingContext, line: string) {
const match = line.match(this.sourceStab);
if (!match) return;
// cf http://www.math.utah.edu/docs/info/stabs_11.html#SEC48
switch (parseInt(match[1])) {
case 68: {
context.source = {file: null, line: parseInt(match[2])};
break;
}
case 132:
case 100: {
context.source = null;
context.prevLabel = '';
break;
}
}
}
protected handle6502(context: ParsingContext, line: string) {
const match = line.match(this.source6502Dbg);
if (match) {
const file = utils.maskRootdir(match[1]);
const sourceLine = parseInt(match[2]);
if (context.dontMaskFilenames) {
context.source = {
file: file,
line: sourceLine,
mainsource: !!this.stdInLooking.test(file),
};
} else {
context.source = {
file: this.stdInLooking.test(file) ? null : file,
line: sourceLine,
};
}
} else if (this.source6502DbgEnd.test(line)) {
context.source = null;
}
}
processAsm(asmResult: string, filters: ParseFiltersAndOutputOptions): ParsedAsmResult {
if (filters.binary || filters.binaryObject) return this.processBinaryAsm(asmResult, filters);
const startTime = process.hrtime.bigint();
if (filters.commentOnly) {
// Remove any block comments that start and end on a line if we're removing comment-only lines.
asmResult = asmResult.replace(this.blockComments, '');
}
const asm: ParsedAsmResultLine[] = [];
const labelDefinitions: Record<string, number> = {};
let asmLines = utils.splitLines(asmResult);
const startingLineCount = asmLines.length;
if (filters.preProcessLines !== undefined) {
asmLines = filters.preProcessLines(asmLines);
}
const labelsUsed = this.findUsedLabels(asmLines, filters.directives);
let mayRemovePreviousLabel = true;
let keepInlineCode = false;
let lastOwnSource: AsmResultSource | undefined | null;
const context: ParsingContext = {
files: this.parseFiles(asmLines),
source: null,
prevLabel: '',
prevLabelIsUserFunction: false,
dontMaskFilenames: filters.dontMaskFilenames || false,
};
function maybeAddBlank() {
const lastBlank = asm.length === 0 || asm[asm.length - 1].text === '';
if (!lastBlank) asm.push({text: '', source: null, labels: []});
}
let inNvccDef = false;
let inNvccCode = false;
let inCustomAssembly = 0;
let inVLIWpacket = false;
let idxLine = 0;
// TODO: Make this function smaller
// eslint-disable-next-line max-statements
while (idxLine < asmLines.length) {
let line = asmLines[idxLine];
idxLine++;
if (line.trim() === '') {
maybeAddBlank();
continue;
}
if (this.startAppBlock.test(line.trim()) || this.startAsmNesting.test(line.trim())) {
inCustomAssembly++;
} else if (this.endAppBlock.test(line.trim()) || this.endAsmNesting.test(line.trim())) {
inCustomAssembly--;
} else {
inVLIWpacket = this.checkVLIWpacket(line, inVLIWpacket);
}
this.handleSource(context, line);
this.handleStabs(context, line);
this.handle6502(context, line);
if (context.source && (context.source.file === null || context.source.mainsource)) {
lastOwnSource = context.source;
}
if (this.endBlock.test(line) || (inNvccCode && /}/.test(line))) {
context.source = null;
context.prevLabel = '';
lastOwnSource = null;
}
const doLibraryFilterCheck = filters.libraryCode && !context.prevLabelIsUserFunction;
if (
doLibraryFilterCheck &&
!lastOwnSource &&
context.source &&
context.source.file !== null &&
!context.source.mainsource
) {
if (mayRemovePreviousLabel && asm.length > 0) {
const lastLine = asm[asm.length - 1];
const labelDef = lastLine.text ? lastLine.text.match(this.labelDef) : null;
if (labelDef) {
asm.pop();
keepInlineCode = false;
delete labelDefinitions[labelDef[1]];
} else {
keepInlineCode = true;
}
mayRemovePreviousLabel = false;
}
if (!keepInlineCode) {
continue;
}
} else {
mayRemovePreviousLabel = true;
}
if (
filters.commentOnly &&
((this.commentOnly.test(line) && !inNvccCode) || (this.commentOnlyNvcc.test(line) && inNvccCode))
) {
continue;
}
if (inCustomAssembly > 0) line = this.fixLabelIndentation(line);
let match = line.match(this.labelDef);
if (!match) match = line.match(this.assignmentDef);
if (!match) {
match = line.match(this.cudaBeginDef);
if (match) {
inNvccDef = true;
inNvccCode = true;
}
}
if (match) {
// It's a label definition.
// g-as shows local labels as eg: "1: call mcount". We characterize such a label as
// "the label-matching part doesn't equal the whole line" and treat it as used.
if (labelsUsed[match[1]] === undefined && match[0] === line) {
// It's an unused label.
if (filters.labels) {
continue;
}
} else {
// A used label.
context.prevLabel = match[1];
labelDefinitions[match[1]] = asm.length + 1;
if (!inNvccDef && !inNvccCode && filters.libraryCode) {
context.prevLabelIsUserFunction = this.isUserFunctionByLookingAhead(context, asmLines, idxLine);
}
}
}
if (inNvccDef) {
if (this.cudaEndDef.test(line)) inNvccDef = false;
} else if (!match && filters.directives) {
// Check for directives only if it wasn't a label; the regexp would
// otherwise misinterpret labels as directives.
if (this.dataDefn.test(line) && context.prevLabel) {
// We're defining data that's being used somewhere.
} else {
// .inst generates an opcode, so does not count as a directive
if (this.directive.test(line) && !this.instOpcodeRe.test(line)) {
continue;
}
}
}
line = utils.expandTabs(line);
const text = AsmRegex.filterAsmLine(line, filters);
const labelsInLine = match ? [] : this.getUsedLabelsInLine(text);
asm.push({
text: text,
source: this.hasOpcode(line, inNvccCode, inVLIWpacket) ? context.source || null : null,
labels: labelsInLine,
});
}
this.removeLabelsWithoutDefinition(asm, labelDefinitions);
const endTime = process.hrtime.bigint();
return {
asm: asm,
labelDefinitions: labelDefinitions,
parsingTime: ((endTime - startTime) / BigInt(1000000)).toString(),
filteredCount: startingLineCount - asm.length,
};
}
fixLabelIndentation(line) {
const match = line.match(this.indentedLabelDef);
if (match) {
return line.replace(/^\s+/, '');
} else {
return line;
}
}
isUserFunction(func) {
if (this.binaryHideFuncRe === null) return true;
return !this.binaryHideFuncRe.test(func);
}
processBinaryAsm(asmResult: string, filters: ParseFiltersAndOutputOptions): ParsedAsmResult {
const startTime = process.hrtime.bigint();
const asm: ParsedAsmResultLine[] = [];
const labelDefinitions: Record<string, number> = {};
const dontMaskFilenames = filters.dontMaskFilenames;
let asmLines = utils.splitLines(asmResult);
const startingLineCount = asmLines.length;
let source: AsmResultSource | undefined | null = null;
let func: string | null = null;
let mayRemovePreviousLabel = true;
// Handle "error" documents.
if (asmLines.length === 1 && asmLines[0][0] === '<') {
return {
asm: [{text: asmLines[0], source: null}],
};
}
if (filters.preProcessBinaryAsmLines !== undefined) {
asmLines = filters.preProcessBinaryAsmLines(asmLines);
}
for (const line of asmLines) {
const labelsInLine: AsmResultLabel[] = [];
if (asm.length >= this.maxAsmLines) {
if (asm.length === this.maxAsmLines) {
asm.push({
text: '[truncated; too many lines]',
source: null,
labels: labelsInLine,
});
}
continue;
}
let match = line.match(this.lineRe);
if (match) {
assert(match.groups);
if (dontMaskFilenames) {
source = {
file: utils.maskRootdir(match[1]),
line: parseInt(match.groups.line),
mainsource: true,
};
} else {
source = {file: null, line: parseInt(match.groups.line), mainsource: true};
}
continue;
}
match = line.match(this.labelRe);
if (match) {
func = match[2];
if (func && this.isUserFunction(func)) {
asm.push({
text: func + ':',
source: null,
labels: labelsInLine,
});
labelDefinitions[func] = asm.length;
if (process.platform === 'win32') source = null;
}
continue;
}
if (func && line === `${func}():`) continue;
if (!func || !this.isUserFunction(func)) continue;
// note: normally the source.file will be null if it's code from example.ext
// but with filters.dontMaskFilenames it will be filled with the actual filename
// instead we can test source.mainsource in that situation
const isMainsource = source && (source.file === null || source.mainsource);
if (filters.libraryCode && !isMainsource) {
if (mayRemovePreviousLabel && asm.length > 0) {
const lastLine = asm[asm.length - 1];
if (lastLine.text && this.labelDef.test(lastLine.text)) {
asm.pop();
}
mayRemovePreviousLabel = false;
}
continue;
} else {
mayRemovePreviousLabel = true;
}
match = line.match(this.asmOpcodeRe);
if (match) {
assert(match.groups);
const address = parseInt(match.groups.address, 16);
const opcodes = (match.groups.opcodes || '').split(' ').filter(x => !!x);
const disassembly = ' ' + AsmRegex.filterAsmLine(match.groups.disasm, filters);
const destMatch = line.match(this.destRe);
if (destMatch) {
const labelName = destMatch[2];
const startCol = disassembly.indexOf(labelName) + 1;
labelsInLine.push({
name: labelName,
range: {
startCol: startCol,
endCol: startCol + labelName.length,
},
});
}
asm.push({
opcodes: opcodes,
address: address,
text: disassembly,
source: source,
labels: labelsInLine,
});
}
match = line.match(this.relocationRe);
if (match) {
assert(match.groups);
const address = parseInt(match.groups.address, 16);
const relocname = match.groups.relocname;
const relocdata = match.groups.relocdata;
// value/addend matched but not used yet.
// const match_value = relocdata.match(this.relocDataSymNameRe);
asm.push({
text: ` ${relocname} ${relocdata}`,
address: address,
});
}
}
this.removeLabelsWithoutDefinition(asm, labelDefinitions);
const endTime = process.hrtime.bigint();
return {
asm: asm,
labelDefinitions: labelDefinitions,
parsingTime: ((endTime - startTime) / BigInt(1000000)).toString(),
filteredCount: startingLineCount - asm.length,
};
}
process(asm: string, filters: ParseFiltersAndOutputOptions) {
return this.processAsm(asm, filters);
}
}