|  | // 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. | 
|  |  | 
|  | import sinon from 'sinon'; | 
|  |  | 
|  | import { BaseCompiler } from '../lib/base-compiler'; | 
|  | import { BuildEnvSetupBase } from '../lib/buildenvsetup'; | 
|  | import { Win32Compiler } from '../lib/compilers/win32'; | 
|  | import * as exec from '../lib/exec'; | 
|  |  | 
|  | import { fs, makeCompilationEnvironment, path, should } from './utils'; | 
|  |  | 
|  | const languages = { | 
|  | 'c++': {id: 'c++'}, | 
|  | }; | 
|  |  | 
|  | describe('Basic compiler invariants', function () { | 
|  | let ce, compiler; | 
|  | const info = { | 
|  | exe: null, | 
|  | remote: true, | 
|  | lang: languages['c++'].id, | 
|  | ldPath: [], | 
|  | }; | 
|  |  | 
|  | before(() => { | 
|  | ce = makeCompilationEnvironment({ languages }); | 
|  | compiler = new BaseCompiler(info, ce); | 
|  | }); | 
|  |  | 
|  | it('should recognize when optOutput has been request', () => { | 
|  | compiler.optOutputRequested(['please', 'recognize', '-fsave-optimization-record']).should.equal(true); | 
|  | compiler.optOutputRequested(['please', "don't", 'recognize']).should.equal(false); | 
|  | }); | 
|  | // Overkill test, but now we're safer! | 
|  | it('should recognize cfg compilers', () => { | 
|  | compiler.isCfgCompiler('clang version 5.0.0 (https://github.com/asutton/clang.git 449c8c3e91355a3b2b6761e11d9fb5d3c125b791) (https://github.com/llvm-mirror/llvm.git 40b1e969f9cb2a0c697e247435193fb006ef1311)').should.equal(true); | 
|  | compiler.isCfgCompiler('clang version 4.0.0 (tags/RELEASE_400/final 299826)').should.equal(true); | 
|  | compiler.isCfgCompiler('clang version 7.0.0 (trunk 325868)').should.equal(true); | 
|  | compiler.isCfgCompiler('clang version 3.3 (tags/RELEASE_33/final)').should.equal(true); | 
|  | compiler.isCfgCompiler('clang version 6.0.0 (tags/RELEASE_600/final 327031) (llvm/tags/RELEASE_600/final 327028)').should.equal(true); | 
|  |  | 
|  | compiler.isCfgCompiler('g++ (GCC-Explorer-Build) 4.9.4').should.equal(true); | 
|  | compiler.isCfgCompiler('g++ (GCC-Explorer-Build) 8.0.1 20180223 (experimental)').should.equal(true); | 
|  | compiler.isCfgCompiler('g++ (GCC-Explorer-Build) 8.0.1 20180223 (experimental)').should.equal(true); | 
|  | compiler.isCfgCompiler('g++ (GCC) 4.1.2').should.equal(true); | 
|  |  | 
|  | compiler.isCfgCompiler('foo-bar-g++ (GCC-Explorer-Build) 4.9.4').should.equal(true); | 
|  | compiler.isCfgCompiler('foo-bar-gcc (GCC-Explorer-Build) 4.9.4').should.equal(true); | 
|  | compiler.isCfgCompiler('foo-bar-gdc (GCC-Explorer-Build) 4.9.4').should.equal(true); | 
|  |  | 
|  | compiler.isCfgCompiler('fake-for-test (Based on g++)').should.equal(false); | 
|  |  | 
|  | compiler.isCfgCompiler('gdc (crosstool-NG 203be35 - 20160205-2.066.1-e95a735b97) 5.2.0').should.equal(true); | 
|  | compiler.isCfgCompiler('gdc (crosstool-NG hg+unknown-20131212.080758 - 20140430-2.064.2-404a037d26) 4.8.2').should.equal(true); | 
|  | compiler.isCfgCompiler('gdc (crosstool-NG crosstool-ng-1.20.0-232-gc746732 - 20150830-2.066.1-d0dd4a83de) 4.9.3').should.equal(true); | 
|  |  | 
|  | compiler.isCfgCompiler('fake-for-test (Based on gdc)').should.equal(false); | 
|  | }); | 
|  | it('should allow comments next to includes (Bug #874)', () => { | 
|  | should.equal(compiler.checkSource('#include <cmath> // std::(sin, cos, ...)'), null); | 
|  | const badSource = compiler.checkSource('#include </dev/null..> //Muehehehe'); | 
|  | should.exist(badSource); | 
|  | badSource.should.equal('<stdin>:1:1: no absolute or relative includes please'); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | describe('Compiler execution', function () { | 
|  | let ce, compiler, compilerNoExec, win32compiler; | 
|  | const executingCompilerInfo = { | 
|  | exe: null, | 
|  | remote: true, | 
|  | lang: languages['c++'].id, | 
|  | ldPath: [], | 
|  | libPath: [], | 
|  | supportsExecute: true, | 
|  | supportsBinary: true, | 
|  | options: '--hello-abc -I"/opt/some thing 1.0/include" -march="magic 8bit"', | 
|  | }; | 
|  | const win32CompilerInfo = { | 
|  | exe: null, | 
|  | remote: true, | 
|  | lang: languages['c++'].id, | 
|  | ldPath: [], | 
|  | supportsExecute: true, | 
|  | supportsBinary: true, | 
|  | options: '/std=c++17 /I"C:/program files (x86)/Company name/Compiler 1.2.3/include" /D "MAGIC=magic 8bit"', | 
|  | }; | 
|  | const noExecuteSupportCompilerInfo = { | 
|  | exe: null, | 
|  | remote: true, | 
|  | lang: languages['c++'].id, | 
|  | ldPath: [], | 
|  | libPath: [], | 
|  | }; | 
|  |  | 
|  | before(() => { | 
|  | ce = makeCompilationEnvironment({ languages }); | 
|  | compiler = new BaseCompiler(executingCompilerInfo, ce); | 
|  | win32compiler = new Win32Compiler(win32CompilerInfo, ce); | 
|  | compilerNoExec = new BaseCompiler(noExecuteSupportCompilerInfo, ce); | 
|  | }); | 
|  |  | 
|  | afterEach(() => sinon.restore()); | 
|  |  | 
|  | function stubOutCallToExec(execStub, compiler, content, result, nthCall) { | 
|  | execStub.onCall(nthCall || 0).callsFake((compiler, args) => { | 
|  | const minusO = args.indexOf('-o'); | 
|  | minusO.should.be.gte(0); | 
|  | const output = args[minusO + 1]; | 
|  | // Maybe we should mock out the FS too; but that requires a lot more work. | 
|  | fs.writeFileSync(output, content); | 
|  | result.filenameTransform = x => x; | 
|  | return Promise.resolve(result); | 
|  | }); | 
|  | } | 
|  |  | 
|  | it('basecompiler should handle spaces in options correctly', () => { | 
|  | const userOptions = []; | 
|  | const filters = {}; | 
|  | const backendOptions = {}; | 
|  | const inputFilename = 'example.cpp'; | 
|  | const outputFilename = 'example.s'; | 
|  | const libraries = []; | 
|  |  | 
|  | const args = compiler.prepareArguments(userOptions, filters, backendOptions, inputFilename, outputFilename, libraries); | 
|  | args.should.deep.equal([ | 
|  | '-g', | 
|  | '-o', | 
|  | 'example.s', | 
|  | '-S', | 
|  | '--hello-abc', | 
|  | '-I/opt/some thing 1.0/include', | 
|  | '-march=magic 8bit', | 
|  | 'example.cpp', | 
|  | ]); | 
|  | }); | 
|  |  | 
|  | it('win32 compiler should handle spaces in options correctly', () => { | 
|  | const userOptions = []; | 
|  | const filters = {}; | 
|  | const backendOptions = {}; | 
|  | const inputFilename = 'example.cpp'; | 
|  | const outputFilename = 'example.s'; | 
|  | const libraries = []; | 
|  |  | 
|  | const win32args = win32compiler.prepareArguments(userOptions, filters, backendOptions, inputFilename, outputFilename, libraries); | 
|  | win32args.should.deep.equal([ | 
|  | '/nologo', | 
|  | '/FA', | 
|  | '/c', | 
|  | '/Faexample.s', | 
|  | '/Foexample.s.obj', | 
|  | '/std=c++17', | 
|  | '/IC:/program files (x86)/Company name/Compiler 1.2.3/include', | 
|  | '/D', | 
|  | 'MAGIC=magic 8bit', | 
|  | 'example.cpp', | 
|  | ]); | 
|  | }); | 
|  |  | 
|  | it('buildenv should handle spaces correctly', () => { | 
|  | const buildenv = new BuildEnvSetupBase(executingCompilerInfo, ce); | 
|  | buildenv.getCompilerArch().should.equal('magic 8bit'); | 
|  | }); | 
|  |  | 
|  | it('should compile', async () => { | 
|  | const execStub = sinon.stub(compiler, 'exec'); | 
|  | stubOutCallToExec(execStub, compiler, 'This is the output file', { | 
|  | code: 0, | 
|  | okToCache: true, | 
|  | stdout: 'stdout', | 
|  | stderr: 'stderr', | 
|  | }); | 
|  | const result = await compiler.compile( | 
|  | 'source', | 
|  | 'options', | 
|  | {}, | 
|  | {}, | 
|  | false, | 
|  | [], | 
|  | {}, | 
|  | []); | 
|  | result.code.should.equal(0); | 
|  | result.compilationOptions.should.contain('options'); | 
|  | result.compilationOptions.should.contain(result.inputFilename); | 
|  | result.okToCache.should.be.true; | 
|  | result.asm.should.deep.equal([{source: null, text: 'This is the output file', labels: []}]); | 
|  | result.stdout.should.deep.equal([{text: 'stdout'}]); | 
|  | result.stderr.should.deep.equal([{text: 'stderr'}]); | 
|  | result.popularArguments.should.deep.equal({}); | 
|  | result.tools.should.deep.equal([]); | 
|  | execStub.called.should.be.true; | 
|  | }); | 
|  |  | 
|  | it('should handle compilation failures', async () => { | 
|  | const execStub = sinon.stub(compiler, 'exec'); | 
|  | stubOutCallToExec(execStub, compiler, 'This is the output file', { | 
|  | code: 1, | 
|  | okToCache: true, | 
|  | stdout: '', | 
|  | stderr: 'oh noes', | 
|  | }); | 
|  | const result = await compiler.compile( | 
|  | 'source', | 
|  | 'options', | 
|  | {}, | 
|  | {}, | 
|  | false, | 
|  | [], | 
|  | {}, | 
|  | []); | 
|  | result.code.should.equal(1); | 
|  | result.asm.should.deep.equal([{labels: [], source: null, text: '<Compilation failed>'}]); | 
|  | }); | 
|  |  | 
|  | it('should cache results (when asked)', async () => { | 
|  | const ceMock = sinon.mock(ce); | 
|  | const fakeExecResults = { | 
|  | code: 0, | 
|  | okToCache: true, | 
|  | stdout: 'stdout', | 
|  | stderr: 'stderr', | 
|  | }; | 
|  | const execStub = sinon.stub(compiler, 'exec'); | 
|  | stubOutCallToExec(execStub, compiler, 'This is the output file', fakeExecResults); | 
|  | const source = 'Some cacheable source'; | 
|  | const options = 'Some cacheable options'; | 
|  | ceMock.expects('cachePut').withArgs(sinon.match({source, options}), sinon.match(fakeExecResults)).resolves(); | 
|  | const uncachedResult = await compiler.compile( | 
|  | source, | 
|  | options, | 
|  | {}, | 
|  | {}, | 
|  | false, | 
|  | [], | 
|  | {}, | 
|  | []); | 
|  | uncachedResult.code.should.equal(0); | 
|  | ceMock.verify(); | 
|  | }); | 
|  |  | 
|  | it('should not cache results (when not asked)', async () => { | 
|  | const ceMock = sinon.mock(ce); | 
|  | const fakeExecResults = { | 
|  | code: 0, | 
|  | okToCache: false, | 
|  | stdout: 'stdout', | 
|  | stderr: 'stderr', | 
|  | }; | 
|  | const execStub = sinon.stub(compiler, 'exec'); | 
|  | stubOutCallToExec(execStub, compiler, 'This is the output file', fakeExecResults); | 
|  | ceMock.expects('cachePut').never(); | 
|  | const source = 'Some cacheable source'; | 
|  | const options = 'Some cacheable options'; | 
|  | const uncachedResult = await compiler.compile( | 
|  | source, | 
|  | options, | 
|  | {}, | 
|  | {}, | 
|  | false, | 
|  | [], | 
|  | {}, | 
|  | []); | 
|  | uncachedResult.code.should.equal(0); | 
|  | ceMock.verify(); | 
|  | }); | 
|  |  | 
|  | it('should read from the cache (when asked)', async () => { | 
|  | const ceMock = sinon.mock(ce); | 
|  | const source = 'Some previously cached source'; | 
|  | const options = 'Some previously cached options'; | 
|  | ceMock.expects('cacheGet').withArgs(sinon.match({source, options})).resolves({code: 123}); | 
|  | const cachedResult = await compiler.compile( | 
|  | source, | 
|  | options, | 
|  | {}, | 
|  | {}, | 
|  | false, | 
|  | [], | 
|  | {}, | 
|  | []); | 
|  | cachedResult.code.should.equal(123); | 
|  | ceMock.verify(); | 
|  | }); | 
|  |  | 
|  | it('should note read from the cache (when bypassed)', async () => { | 
|  | const ceMock = sinon.mock(ce); | 
|  | const fakeExecResults = { | 
|  | code: 0, | 
|  | okToCache: true, | 
|  | stdout: 'stdout', | 
|  | stderr: 'stderr', | 
|  | }; | 
|  | const source = 'Some previously cached source'; | 
|  | const options = 'Some previously cached options'; | 
|  | ceMock.expects('cacheGet').never(); | 
|  | const execStub = sinon.stub(compiler, 'exec'); | 
|  | stubOutCallToExec(execStub, compiler, 'This is the output file', fakeExecResults); | 
|  | const uncachedResult = await compiler.compile( | 
|  | source, | 
|  | options, | 
|  | {}, | 
|  | {}, | 
|  | true, | 
|  | [], | 
|  | {}, | 
|  | []); | 
|  | uncachedResult.code.should.equal(0); | 
|  | ceMock.verify(); | 
|  | }); | 
|  |  | 
|  | it('should execute', async () => { | 
|  | const execMock = sinon.mock(exec); | 
|  | const execStub = sinon.stub(compiler, 'exec'); | 
|  | stubOutCallToExec(execStub, compiler, 'This is the output asm file', { | 
|  | code: 0, | 
|  | okToCache: true, | 
|  | stdout: 'asm stdout', | 
|  | stderr: 'asm stderr', | 
|  | }, 0); | 
|  | stubOutCallToExec(execStub, compiler, 'This is the output binary file', { | 
|  | code: 0, | 
|  | okToCache: true, | 
|  | stdout: 'binary stdout', | 
|  | stderr: 'binary stderr', | 
|  | }, 1); | 
|  | execMock.expects('sandbox').withArgs(sinon.match.string, sinon.match.array, sinon.match.object).resolves({ | 
|  | code: 0, | 
|  | stdout: 'exec stdout', | 
|  | stderr: 'exec stderr', | 
|  | }); | 
|  | const result = await compiler.compile( | 
|  | 'source', | 
|  | 'options', | 
|  | {}, | 
|  | {execute: true}, | 
|  | false, | 
|  | [], | 
|  | {}, | 
|  | []); | 
|  | result.code.should.equal(0); | 
|  | result.execResult.didExecute.should.be.true; | 
|  | result.stdout.should.deep.equal([{text: 'asm stdout'}]); | 
|  | result.execResult.stdout.should.deep.equal([{text: 'exec stdout'}]); | 
|  | result.execResult.buildResult.stdout.should.deep.equal([{text: 'binary stdout'}]); | 
|  | result.stderr.should.deep.equal([{text: 'asm stderr'}]); | 
|  | result.execResult.stderr.should.deep.equal([{text: 'exec stderr'}]); | 
|  | result.execResult.buildResult.stderr.should.deep.equal([{text: 'binary stderr'}]); | 
|  | }); | 
|  |  | 
|  | it('should not execute where not supported', async () => { | 
|  | const execMock = sinon.mock(exec); | 
|  | const execStub = sinon.stub(compilerNoExec, 'exec'); | 
|  | stubOutCallToExec(execStub, compilerNoExec, 'This is the output asm file', { | 
|  | code: 0, | 
|  | okToCache: true, | 
|  | stdout: 'asm stdout', | 
|  | stderr: 'asm stderr', | 
|  | }, 0); | 
|  | stubOutCallToExec(execStub, compilerNoExec, 'This is the output binary file', { | 
|  | code: 0, | 
|  | okToCache: true, | 
|  | stdout: 'binary stdout', | 
|  | stderr: 'binary stderr', | 
|  | }, 1); | 
|  | execMock.expects('sandbox').withArgs(sinon.match.string, sinon.match.array, sinon.match.object).resolves({ | 
|  | code: 0, | 
|  | stdout: 'exec stdout', | 
|  | stderr: 'exec stderr', | 
|  | }); | 
|  | const result = await compilerNoExec.compile( | 
|  | 'source', | 
|  | 'options', | 
|  | {}, | 
|  | {execute: true}, | 
|  | false, | 
|  | [], | 
|  | {}, | 
|  | []); | 
|  | result.code.should.equal(0); | 
|  | result.execResult.didExecute.should.be.false; | 
|  | result.stdout.should.deep.equal([{text: 'asm stdout'}]); | 
|  | result.execResult.stdout.should.deep.equal([]); | 
|  | result.execResult.buildResult.stdout.should.deep.equal([{text: 'binary stdout'}]); | 
|  | result.stderr.should.deep.equal([{text: 'asm stderr'}]); | 
|  | result.execResult.stderr.should.deep.equal([{text: 'Compiler does not support execution'}]); | 
|  | result.execResult.buildResult.stderr.should.deep.equal([{text: 'binary stderr'}]); | 
|  | }); | 
|  |  | 
|  | it('should demangle', async () => { | 
|  | const withDemangler = {...noExecuteSupportCompilerInfo, demangler: 'demangler-exe', demanglerType: 'cpp'}; | 
|  | const compiler = new BaseCompiler(withDemangler, ce); | 
|  | const execStub = sinon.stub(compiler, 'exec'); | 
|  | stubOutCallToExec(execStub, compiler, 'someMangledSymbol:\n', { | 
|  | code: 0, | 
|  | okToCache: true, | 
|  | stdout: 'stdout', | 
|  | stderr: 'stderr', | 
|  | }); | 
|  | execStub.onCall(1).callsFake((demangler, args, options) => { | 
|  | demangler.should.equal('demangler-exe'); | 
|  | options.input.should.equal('someMangledSymbol'); | 
|  | return Promise.resolve({ | 
|  | code: 0, | 
|  | filenameTransform: x => x, | 
|  | stdout: 'someDemangledSymbol\n', | 
|  | stderr: '', | 
|  | }); | 
|  | }); | 
|  |  | 
|  | const result = await compiler.compile( | 
|  | 'source', | 
|  | 'options', | 
|  | {}, | 
|  | {demangle: true}, | 
|  | false, | 
|  | [], | 
|  | {}, | 
|  | []); | 
|  | result.code.should.equal(0); | 
|  | result.asm.should.deep.equal([{source: null, labels: [], text: 'someDemangledSymbol:'}]); | 
|  | // TODO all with demangle: false | 
|  | }); | 
|  |  | 
|  | it('should run objdump properly', async () => { | 
|  | const withDemangler = {...noExecuteSupportCompilerInfo, objdumper: 'objdump-exe'}; | 
|  | const compiler = new BaseCompiler(withDemangler, ce); | 
|  | const execStub = sinon.stub(compiler, 'exec'); | 
|  | execStub.onCall(0).callsFake((objdumper, args, options) => { | 
|  | objdumper.should.equal('objdump-exe'); | 
|  | args.should.deep.equal([ | 
|  | '-d', 'output', | 
|  | '-l', '--insn-width=16', | 
|  | '-C', '-M', 'intel']); | 
|  | options.maxOutput.should.equal(123456); | 
|  | return Promise.resolve({ | 
|  | code: 0, | 
|  | filenameTransform: x => x, | 
|  | stdout: 'the output', | 
|  | stderr: '', | 
|  | }); | 
|  | }); | 
|  | compiler.supportsObjdump().should.be.true; | 
|  | const result = await compiler.objdump( | 
|  | 'output', | 
|  | {}, | 
|  | 123456, | 
|  | true, | 
|  | true); | 
|  | result.asm.should.deep.equal('the output'); | 
|  | }); | 
|  |  | 
|  | it('should run process opt output', async () => { | 
|  | const test = `--- !Missed | 
|  | Pass: inline | 
|  | Name: NeverInline | 
|  | DebugLoc: { File: example.cpp, Line: 4, Column: 21 } | 
|  | Function: main | 
|  | Args: [] | 
|  | ... | 
|  | `; | 
|  | const dirPath = await compiler.newTempDir(); | 
|  | const optPath = path.join(dirPath, 'temp.out'); | 
|  | await fs.writeFile(optPath, test); | 
|  | const a = await compiler.processOptOutput(optPath); | 
|  | a.should.deep.equal([{ | 
|  | Args: [], | 
|  | DebugLoc: {Column: 21, File: 'example.cpp', Line: 4}, | 
|  | Function: 'main', | 
|  | Name: 'NeverInline', | 
|  | Pass: 'inline', | 
|  | displayString: '', | 
|  | optType: 'Missed', | 
|  | }]); | 
|  | }); | 
|  | }); |