blob: 34216f204720c1a8a9a19c0e996496bfcea76105 [file] [log] [blame] [raw]
// Copyright (c) 2018, Microsoft Corporation
// 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.
const AsmParserBase = require('./asm-parser'),
logger = require('./logger').logger,
utils = require('./utils'),
AsmRegex = require('./asmregex').AsmRegex;
class AsmParser extends AsmParserBase {
constructor(compilerProps) {
super(compilerProps);
this.asmBinaryParser = new AsmParserBase(compilerProps);
this.miscDirective = /^\s*(include|INCLUDELIB|TITLE|\.|THUMB|ARM64|TTL|END$)/;
this.localLabelDef = /^([a-zA-Z$_]+) =/;
this.commentOnly = /^;/;
this.filenameComment = /^; File (.+)/;
this.lineNumberComment = /^; Line ([0-9]+)/;
this.beginSegment = /^(CONST|_BSS|\.?[prx]?data(\$[a-zA-Z]+)?|CRT(\$[a-zA-Z]+)?|_TEXT|\.?text(\$[a-zA-Z]+)?)\s+SEGMENT|\s*AREA/;
this.endSegment = /^(CONST|_BSS|[prx]?data(\$[a-zA-Z]+)?|CRT(\$[a-zA-Z]+)?|_TEXT|text(\$[a-zA-Z]+)?)\s+ENDS/;
this.beginFunction = /^; Function compile flags: /;
this.endProc = /^([a-zA-Z@$?_][a-zA-Z0-9@$?_<>]*)?\s+ENDP/;
// on x86, we use the end of the segment to end a function
// on arm, we use ENDP
this.endFunction = /^(_TEXT\s+ENDS|\s+ENDP)/;
this.labelDef = /^\|?([a-zA-Z@$?_][a-zA-Z0-9@$?_<>]*)\|?\s+(PROC|=|D[BWDQ])/;
this.definesGlobal = /^\s*(PUBLIC|EXTRN|EXPORT)\s+/;
this.definesFunction = /^\|?([a-zA-Z@$?_][a-zA-Z0-9@$?_<>]*)\|?\s+PROC/;
this.labelFind = /[a-zA-Z@$?_][a-zA-Z0-9@$?_<>]*/g;
this.dataDefn = /^(\|?[a-zA-Z@$?_][a-zA-Z0-9@$?_<>]*\|?)\sDC?[BWDQ]\s|\s+DC?[BWDQ]\s|\s+ORG/;
// these are set to an impossible regex, because VC doesn't have inline assembly
this.startAppBlock = this.startAsmNesting = /a^/;
this.endAppBLock = this.endAsmNesting = /a^/;
// same, but for CUDA
this.cudaBeginDef = /a^/;
}
hasOpcode(line) {
// note: cl doesn't output leading labels
// strip comments
line = line.split(';', 1)[0];
// check for empty lines
if (line.length === 0) return false;
// check for a local label definition
if (line.match(this.localLabelDef)) return false;
// check for global label definitions
if (line.match(this.definesGlobal)) return false;
// check for data definitions
if (line.match(this.dataDefn)) return false;
// check for segment begin and end
if (line.match(this.beginSegment) || line.match(this.endSegment)) return false;
// check for function begin and end
// note: functionBegin is used for the function compile flags comment
if (line.match(this.definesFunction) || line.match(this.endProc)) return false;
// check for miscellaneous directives
if (line.match(this.miscDirective)) return false;
return !!line.match(this.hasOpcodeRe);
}
labelFindFor() {
return this.labelFind;
}
processAsm(asm, filters) {
if (filters.binary) {
return this.asmBinaryParser.processAsm(asm, filters);
}
const getFilenameFromComment = line => {
const matches = line.match(this.filenameComment);
if (!matches) {
return null;
} else {
return matches[1];
}
};
const getLineNumberFromComment = line => {
const matches = line.match(this.lineNumberComment);
if (!matches) {
return null;
} else {
return parseInt(matches[1]);
}
};
const asmLines = utils.splitLines(asm);
// note: VC doesn't output unused labels, afaict
const stdInLooking = /<stdin>|^-$|example\.[^/]+$|<source>/;
// type source = {file: string option; line: int}
// type line = {line: string; source: source option}
// type func =
// { lines: line array
// ; name: string | undefined
// ; initialLine: int
// ; file: string option | undefined }
let resultObject = {
prefix: [], // line array
functions: [], // func array
postfix: null // line?
};
let currentFunction = null; // func option
let currentFile = undefined;
let currentLine = undefined;
let seenEnd = false;
const datadefLabels = [];
const datadefLabelsUsed = [];
const createSourceFor = (hasopc, currentFile, currentLine) => {
if (hasopc && (currentFile || currentLine)) {
return {
file: (currentFile ? currentFile : null),
line: (currentLine ? currentLine : null)
};
}
return null;
};
const checkUsedDatadefLabels = (line) => {
const labels = line.match(this.labelFind);
if (!labels) return;
labels.splice(0, 1);
labels.forEach((item) => {
if (datadefLabels.find(l => item === l)) {
datadefLabelsUsed.push(item);
}
});
};
const checkBeginFunction = (line) => {
if (line.match(this.beginFunction)) {
currentFunction = {
lines: [],
initialLine: undefined,
name: undefined,
file: undefined
};
resultObject.functions.push(currentFunction);
}
};
const checkForDdefLabel = (line) => {
const ddef = line.match(this.dataDefn);
if (ddef && ddef[1]) {
datadefLabels.push(ddef[1]);
}
};
asmLines.forEach(line => {
if (line.trim() === "END") {
seenEnd = true;
if (!filters.directives) {
resultObject.postfix = {text: line, source: null};
}
return;
}
if (line.trim() === "") {
if (seenEnd) return;
const emptyLine = {text: "", source: null};
if (currentFunction === null) {
resultObject.prefix.push(emptyLine);
} else {
currentFunction.lines.push(emptyLine);
}
return;
}
if (seenEnd) {
// this should never happen
throw Error("Visual C++: text after the end statement");
}
let tmp = getFilenameFromComment(line);
if (tmp !== null) {
if (currentFunction === null) {
logger.error("We have a file comment outside of a function: %s",
line);
}
// if the file is the "main file", give it the file `null`
if (tmp.match(stdInLooking)) {
currentFile = null;
} else {
currentFile = tmp;
}
if (currentFunction.file === undefined) {
currentFunction.file = currentFile;
}
} else {
tmp = getLineNumberFromComment(line);
if (tmp !== null) {
if (currentFile === undefined) {
logger.error("Somehow, we have a line number comment without a file comment: %s",
line);
}
if (currentFunction.initialLine === undefined) {
currentFunction.initialLine = tmp;
}
currentLine = tmp;
}
}
checkBeginFunction(line);
const functionName = line.match(this.definesFunction);
if (functionName) {
if (asmLines.length === 0) {
return;
}
currentFunction.name = functionName[1];
}
if (filters.commentOnly && line.match(this.commentOnly)) return;
const shouldSkip = filters.directives && (
line.match(this.endSegment) ||
line.match(this.definesGlobal) ||
line.match(this.miscDirective) ||
line.match(this.beginSegment));
if (shouldSkip) {
return;
}
checkForDdefLabel(line);
line = utils.expandTabs(line);
const hasopc = this.hasOpcode(line);
const textAndSource = {
text: AsmRegex.filterAsmLine(line, filters),
source: createSourceFor(hasopc, currentFile, currentLine)
};
if (currentFunction === null) {
resultObject.prefix.push(textAndSource);
} else if (!shouldSkip) {
currentFunction.lines.push(textAndSource);
}
checkUsedDatadefLabels(line);
});
return this.resultObjectIntoArray(resultObject, filters, datadefLabelsUsed);
}
resultObjectIntoArray(obj, filters, ddefLabelsUsed) {
obj.functions.sort((f1, f2) => {
// order the main file above all others
if (f1.file === null && f2.file !== null) {
return -1;
}
if (f1.file !== null && f2.file === null) {
return 1;
}
// order no-file below all others
if (f1.file === undefined && f2.file !== undefined) {
return 1;
}
if (f1.file !== undefined && f2.file === undefined) {
return -1;
}
// if the files are the same, use line number ordering
if (f1.file === f2.file) {
// if the lines are the same as well, it's either:
// - two template instantiations, or
// - two compiler generated functions
// order by name
if (f1.initialLine === f2.initialLine) {
return f1.name.localeCompare(f2.name);
} else {
return f1.initialLine - f2.initialLine;
}
}
// else, order by file
return f1.file.localeCompare(f2.file);
});
let result = [];
let lastLineWasWhitespace = true;
let pushLine = line => {
if (line.text.trim() === '') {
if (!lastLineWasWhitespace) {
result.push({text: "", source: null});
lastLineWasWhitespace = true;
}
} else {
result.push(line);
lastLineWasWhitespace = false;
}
};
if (filters.labels) {
let currentDdef = false;
let isUsed = false;
for (const line of obj.prefix) {
const matches = line.text.match(this.dataDefn);
if (matches) {
if (matches[1]) {
currentDdef = matches[1];
isUsed = ddefLabelsUsed.find(label => currentDdef === label);
}
if (isUsed) {
pushLine(line);
}
} else {
currentDdef = false;
pushLine(line);
}
}
} else {
for (const line of obj.prefix) {
pushLine(line);
}
}
for (const func of obj.functions) {
if (!filters.libraryCode || func.file === null) {
pushLine({text: "", source: null});
for (const line of func.lines) {
pushLine(line);
}
}
}
if (obj.postfix !== null) {
pushLine(obj.postfix);
}
return result;
}
}
module.exports = AsmParser;