|  | // Copyright (c) 2018, 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. | 
|  |  | 
|  | const | 
|  | chai = require('chai'), | 
|  | fs = require('fs'), | 
|  | path = require('path'), | 
|  | utils = require('../lib/utils'), | 
|  | chaiAsPromised = require('chai-as-promised'), | 
|  | SymbolStore = require('../lib/symbol-store').SymbolStore, | 
|  | Demangler = require('../lib/demangler-cpp').Demangler, | 
|  | DemanglerWin32 = require('../lib/demangler-win32').Demangler, | 
|  | exec = require('../lib/exec'), | 
|  | logger = require('../lib/logger').logger; | 
|  |  | 
|  | chai.use(chaiAsPromised); | 
|  | const should = chai.should(); | 
|  | const expect = chai.expect; | 
|  | const assert = chai.assert; | 
|  |  | 
|  | const cppfiltpath = "c++filt"; | 
|  |  | 
|  | class DummyCompiler { | 
|  | exec(command, args, options) { | 
|  | return exec.execute(command, args, options); | 
|  | } | 
|  | } | 
|  |  | 
|  | const catchCppfiltNonexistence = err => { | 
|  | if (!err.message.startsWith('spawn c++filt')) { | 
|  | throw err; | 
|  | } | 
|  | }; | 
|  |  | 
|  | describe('Basic demangling', function () { | 
|  | it('One line of asm', function () { | 
|  | const result = {}; | 
|  | result.asm = [{"text": "Hello, World!"}]; | 
|  |  | 
|  | const demangler = new Demangler(cppfiltpath, new DummyCompiler()); | 
|  | demangler.demanglerArguments = ['-n']; | 
|  |  | 
|  | return Promise.all([ | 
|  | demangler.process(result).then((output) => { | 
|  | output.asm[0].text.should.equal("Hello, World!"); | 
|  | }) | 
|  | ]); | 
|  | }); | 
|  |  | 
|  | it('One label and some asm', function () { | 
|  | const result = {}; | 
|  | result.asm = [ | 
|  | {"text": "_Z6squarei:"}, | 
|  | {"text": "  ret"} | 
|  | ]; | 
|  |  | 
|  | const demangler = new Demangler(cppfiltpath, new DummyCompiler()); | 
|  | demangler.demanglerArguments = ['-n']; | 
|  |  | 
|  | return Promise.all([ | 
|  | demangler.process(result) | 
|  | .then((output) => { | 
|  | output.asm[0].text.should.equal("square(int):"); | 
|  | output.asm[1].text.should.equal("  ret"); | 
|  | }) | 
|  | .catch(catchCppfiltNonexistence) | 
|  | ]); | 
|  | }); | 
|  |  | 
|  | it('One label and use of a label', function () { | 
|  | const result = {}; | 
|  | result.asm = [ | 
|  | {"text": "_Z6squarei:"}, | 
|  | {"text": "  mov eax, $_Z6squarei"} | 
|  | ]; | 
|  |  | 
|  | const demangler = new Demangler(cppfiltpath, new DummyCompiler()); | 
|  | demangler.demanglerArguments = ['-n']; | 
|  |  | 
|  | return Promise.all([ | 
|  | demangler.process(result).then((output) => { | 
|  | output.asm[0].text.should.equal("square(int):"); | 
|  | output.asm[1].text.should.equal("  mov eax, $square(int)"); | 
|  | }) | 
|  | .catch(catchCppfiltNonexistence) | 
|  | ]); | 
|  | }); | 
|  |  | 
|  | it('Two destructors', function () { | 
|  | const result = {}; | 
|  | result.asm = [ | 
|  | {"text": "_ZN6NormalD0Ev:"}, | 
|  | {"text": "  callq _ZdlPv"}, | 
|  | {"text": "_Z7caller1v:"}, | 
|  | {"text": "  rep ret"}, | 
|  | {"text": "_Z7caller2P6Normal:"}, | 
|  | {"text": "  cmp rax, OFFSET FLAT:_ZN6NormalD0Ev"}, | 
|  | {"text": "  jmp _ZdlPvm"}, | 
|  | {"text": "_ZN6NormalD2Ev:"}, | 
|  | {"text": "  rep ret"} | 
|  | ]; | 
|  |  | 
|  | const demangler = new Demangler(cppfiltpath, new DummyCompiler()); | 
|  | demangler.demanglerArguments = ['-n']; | 
|  |  | 
|  | return demangler.process(result) | 
|  | .then((output) => { | 
|  | output.asm[0].text.should.equal("Normal::~Normal() [deleting destructor]:"); | 
|  | output.asm[1].text.should.equal("  callq operator delete(void*)"); | 
|  | output.asm[6].text.should.equal("  jmp operator delete(void*, unsigned long)"); | 
|  | }) | 
|  | .catch(catchCppfiltNonexistence) | 
|  | }); | 
|  |  | 
|  | it('Should ignore comments (CL)', function () { | 
|  | const result = {}; | 
|  | result.asm = [ | 
|  | {"text": "        call     ??3@YAXPEAX_K@Z                ; operator delete"} | 
|  | ]; | 
|  |  | 
|  | const demangler = new DemanglerWin32(cppfiltpath, new DummyCompiler()); | 
|  | demangler.result = result; | 
|  | demangler.symbolstore = new SymbolStore(); | 
|  | demangler.collectLabels(); | 
|  |  | 
|  | const output = demangler.win32RawSymbols; | 
|  | output.should.deep.equal( | 
|  | [ | 
|  | "??3@YAXPEAX_K@Z" | 
|  | ] | 
|  | ); | 
|  | }); | 
|  |  | 
|  | it('Should ignore comments (CPP)', function () { | 
|  | const result = {}; | 
|  | result.asm = [ | 
|  | {"text": "        call     hello                ; operator delete"} | 
|  | ]; | 
|  |  | 
|  | const demangler = new Demangler(cppfiltpath, new DummyCompiler()); | 
|  | demangler.demanglerArguments = ['-n']; | 
|  | demangler.result = result; | 
|  | demangler.symbolstore = new SymbolStore(); | 
|  | demangler.collectLabels(); | 
|  |  | 
|  | const output = demangler.othersymbols.listSymbols(); | 
|  | output.should.deep.equal( | 
|  | [ | 
|  | "hello" | 
|  | ] | 
|  | ); | 
|  | }); | 
|  |  | 
|  | it('Should also support ARM branch instructions', () => { | 
|  | const result = {}; | 
|  | result.asm = [ | 
|  | {"text": "   bl _ZN3FooC1Ev"} | 
|  | ]; | 
|  |  | 
|  | const demangler = new Demangler(cppfiltpath, new DummyCompiler()); | 
|  | demangler.demanglerArguments = ['-n']; | 
|  | demangler.result = result; | 
|  | demangler.symbolstore = new SymbolStore(); | 
|  | demangler.collectLabels(); | 
|  |  | 
|  | const output = demangler.othersymbols.listSymbols(); | 
|  | output.should.deep.equal( | 
|  | [ | 
|  | "_ZN3FooC1Ev" | 
|  | ] | 
|  | ); | 
|  | }); | 
|  |  | 
|  | it('Should NOT handle undecorated labels', () => { | 
|  | const result = {}; | 
|  | result.asm = [ | 
|  | {"text": "$LN3@caller2:"} | 
|  | ]; | 
|  |  | 
|  | const demangler = new DemanglerWin32(cppfiltpath, new DummyCompiler()); | 
|  | demangler.result = result; | 
|  | demangler.symbolstore = new SymbolStore(); | 
|  | demangler.collectLabels(); | 
|  |  | 
|  | const output = demangler.win32RawSymbols; | 
|  | output.should.deep.equal( | 
|  | [ | 
|  | ] | 
|  | ); | 
|  | }); | 
|  |  | 
|  | it('Should ignore comments after jmps', function () { | 
|  | const result = {}; | 
|  | result.asm = [ | 
|  | {"text": "  jmp _Z1fP6mytype # TAILCALL"} | 
|  | ]; | 
|  |  | 
|  | const demangler = new Demangler(cppfiltpath, new DummyCompiler()); | 
|  | demangler.demanglerArguments = ['-n']; | 
|  | demangler.result = result; | 
|  | demangler.symbolstore = new SymbolStore(); | 
|  | demangler.collectLabels(); | 
|  |  | 
|  | const output = demangler.othersymbols.listSymbols(); | 
|  | output.should.deep.equal( | 
|  | [ | 
|  | "_Z1fP6mytype" | 
|  | ] | 
|  | ); | 
|  | }); | 
|  |  | 
|  | it('Should still work with normal jmps', function () { | 
|  | const result = {}; | 
|  | result.asm = [ | 
|  | {"text": "  jmp _Z1fP6mytype"} | 
|  | ]; | 
|  |  | 
|  | const demangler = new Demangler(cppfiltpath, new DummyCompiler()); | 
|  | demangler.demanglerArguments = ['-n']; | 
|  | demangler.result = result; | 
|  | demangler.symbolstore = new SymbolStore(); | 
|  | demangler.collectLabels(); | 
|  |  | 
|  | const output = demangler.othersymbols.listSymbols(); | 
|  | output.should.deep.equal( | 
|  | [ | 
|  | "_Z1fP6mytype" | 
|  | ] | 
|  | ); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | function DoDemangleTest(root, filename, resolve, reject) { | 
|  | fs.readFile(path.join(root, filename), function(err, dataIn) { | 
|  | if (err) reject(err); | 
|  |  | 
|  | let resultIn = {"asm": []}; | 
|  |  | 
|  | resultIn.asm = utils.splitLines(dataIn.toString()).map(function(line) { | 
|  | return {"text": line}; | 
|  | }); | 
|  |  | 
|  | fs.readFile(path.join(root, filename + ".demangle"), function(err, dataOut) { | 
|  | if (err) reject(err); | 
|  |  | 
|  | let resultOut = {"asm": []}; | 
|  | resultOut.asm = utils.splitLines(dataOut.toString()).map(function(line) { | 
|  | return {"text": line}; | 
|  | }); | 
|  |  | 
|  | const demangler = new Demangler(cppfiltpath, new DummyCompiler()); | 
|  | demangler.demanglerArguments = ['-n']; | 
|  | demangler.process(resultIn) | 
|  | .then((output) => { | 
|  | try { | 
|  | output.should.deep.equal(resultOut); | 
|  | resolve(); | 
|  | } catch(err) { | 
|  | reject(err); | 
|  | } | 
|  | }); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | describe('File demangling', function() { | 
|  | const testcasespath = __dirname + '/demangle-cases'; | 
|  |  | 
|  | files = fs.readdirSync(testcasespath); | 
|  |  | 
|  | files.forEach((filename) => { | 
|  | if (filename.endsWith(".asm")) { | 
|  | it(filename, (done) => { | 
|  | DoDemangleTest(testcasespath, filename, () => done(), (err) => done(err)); | 
|  | }); | 
|  | } | 
|  | }); | 
|  | }); |