|  | // 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. | 
|  |  | 
|  | import { logger } from '../logger'; | 
|  | import * as utils from '../utils'; | 
|  |  | 
|  | import { AsmParser } from './asm-parser'; | 
|  | import { AsmRegex } from './asmregex'; | 
|  |  | 
|  | export class VcAsmParser extends AsmParser { | 
|  | constructor(compilerProps) { | 
|  | super(compilerProps); | 
|  | this.asmBinaryParser = new AsmParser(compilerProps); | 
|  | this.miscDirective = /^\s*(include|INCLUDELIB|TITLE|\.|THUMB|ARM64|TTL|END$)/; | 
|  | this.localLabelDef = /^([$A-Z_a-z]+) =/; | 
|  | this.commentOnly = /^;/; | 
|  | this.filenameComment = /^; File (.+)/; | 
|  | this.lineNumberComment = /^; Line (\d+)/; | 
|  | 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-Z_a-z][\w$<>?@]*)?\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-Z_a-z][\w$<>?@]*)\|?\s+(PROC|=|D[BDQW])/; | 
|  | this.definesGlobal = /^\s*(PUBLIC|EXTRN|EXPORT)\s+/; | 
|  | this.definesFunction = /^\|?([$?@A-Z_a-z][\w$<>?@]*)\|?\s+PROC/; | 
|  | this.labelFind = /[$?@A-Z_a-z][\w$<>?@]*/g; | 
|  | this.dataDefn = /^(\|?[$?@A-Z_a-z][\w$<>?@]*\|?)\sDC?[BDQW]\s|\s+DC?[BDQW]\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 (this.localLabelDef.test(line)) return false; | 
|  | // check for global label definitions | 
|  | if (this.definesGlobal.test(line)) return false; | 
|  | // check for data definitions | 
|  | if (this.dataDefn.test(line)) return false; | 
|  | // check for segment begin and end | 
|  | if (this.beginSegment.test(line) || this.endSegment.test(line)) return false; | 
|  | // check for function begin and end | 
|  | // note: functionBegin is used for the function compile flags comment | 
|  | if (this.definesFunction.test(line) || this.endProc.test(line)) return false; | 
|  | // check for miscellaneous directives | 
|  | if (this.miscDirective.test(line)) return false; | 
|  |  | 
|  | return !!this.hasOpcodeRe.test(line); | 
|  | } | 
|  |  | 
|  | 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; | 
|  | let currentLine; | 
|  |  | 
|  | 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); | 
|  | for (const item of labels) { | 
|  | if (datadefLabels.find(l => item === l)) { | 
|  | datadefLabelsUsed.push(item); | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | const checkBeginFunction = (line) => { | 
|  | if (this.beginFunction.test(line)) { | 
|  | 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]); | 
|  | } | 
|  | }; | 
|  |  | 
|  | for (let line of asmLines) { | 
|  | if (line.trim() === 'END') { | 
|  | seenEnd = true; | 
|  | if (!filters.directives) { | 
|  | resultObject.postfix = {text: line, source: null}; | 
|  | } | 
|  | continue; | 
|  | } | 
|  | if (line.trim() === '') { | 
|  | if (seenEnd) continue; | 
|  |  | 
|  | const emptyLine = {text: '', source: null}; | 
|  | if (currentFunction === null) { | 
|  | resultObject.prefix.push(emptyLine); | 
|  | } else { | 
|  | currentFunction.lines.push(emptyLine); | 
|  | } | 
|  | continue; | 
|  | } | 
|  | if (seenEnd) { | 
|  | // this should never happen | 
|  | throw new 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 (stdInLooking.test(tmp)) { | 
|  | 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) { | 
|  | continue; | 
|  | } | 
|  | currentFunction.name = functionName[1]; | 
|  | } | 
|  |  | 
|  | if (filters.commentOnly && this.commentOnly.test(line)) continue; | 
|  |  | 
|  | const shouldSkip = filters.directives && ( | 
|  | line.match(this.endSegment) || | 
|  | line.match(this.definesGlobal) || | 
|  | line.match(this.miscDirective) || | 
|  | line.match(this.beginSegment)); | 
|  |  | 
|  | if (shouldSkip) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | 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) { | 
|  | const collator = new Intl.Collator(); | 
|  |  | 
|  | 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 collator.compare(f1.name, f2.name); | 
|  | } else { | 
|  | return f1.initialLine - f2.initialLine; | 
|  | } | 
|  | } | 
|  |  | 
|  | // else, order by file | 
|  | return collator.compare(f1.file, 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 { | 
|  | asm: result, | 
|  | }; | 
|  | } | 
|  | } |