mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2025-12-27 09:23:52 -05:00
240 lines
9.2 KiB
TypeScript
240 lines
9.2 KiB
TypeScript
// Copyright (c) 2022, 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 path from 'node:path';
|
|
|
|
import {splitArguments} from '../../shared/common-utils.js';
|
|
import {CompilationInfo, ExecutionOptions} from '../../types/compilation/compilation.interfaces.js';
|
|
import type {
|
|
Fix,
|
|
Link,
|
|
MessageWithLocation,
|
|
ResultLine,
|
|
ResultLineTag,
|
|
} from '../../types/resultline/resultline.interfaces.js';
|
|
import type {Artifact, ToolInfo, ToolResult} from '../../types/tool.interfaces.js';
|
|
import {OptionsHandlerLibrary} from '../options-handler.js';
|
|
import * as utils from '../utils.js';
|
|
|
|
import {ToolEnv} from './base-tool.interface.js';
|
|
import {BaseTool} from './base-tool.js';
|
|
|
|
export class SonarTool extends BaseTool {
|
|
reproducer?: Artifact;
|
|
lang: string;
|
|
|
|
static get key() {
|
|
return 'sonar-tool';
|
|
}
|
|
|
|
constructor(toolInfo: ToolInfo, env: ToolEnv) {
|
|
super(toolInfo, env);
|
|
|
|
this.addOptionsToToolArgs = false;
|
|
this.parseOutput = this.parseOutputAndFixes;
|
|
this.lang = 'cpp';
|
|
}
|
|
|
|
makeURL(rule: string, lang: string): string {
|
|
return `https://rules.sonarsource.com/${lang}/RSPEC-${rule.substring(1)}`;
|
|
}
|
|
|
|
readTag(tag: any, output: ResultLine[], severity: number, lang: string) {
|
|
const text = `${tag.text} (${lang}:${tag.ruleKey})`;
|
|
const fixes: Fix[] = tag.fixes.map(f => ({
|
|
title: f.message,
|
|
edits: f.edits.map(e => ({
|
|
line: e.startLine,
|
|
column: e.startColumn,
|
|
text: e.text,
|
|
endline: e.endLine,
|
|
endcolumn: e.endColumn,
|
|
})),
|
|
}));
|
|
const flow: MessageWithLocation[] = tag.parts.map(p => ({
|
|
line: p.line,
|
|
column: p.column,
|
|
file: p.filename,
|
|
text: p.text,
|
|
endline: p.endLine,
|
|
endcolumn: p.endColumn,
|
|
}));
|
|
const link: Link = {
|
|
text: 'More...',
|
|
url: this.makeURL(tag.ruleKey, lang),
|
|
};
|
|
const cetag: ResultLineTag = {
|
|
severity,
|
|
text,
|
|
line: tag.line,
|
|
column: tag.column,
|
|
endline: tag.endLine,
|
|
endcolumn: tag.endColumn,
|
|
link,
|
|
fixes,
|
|
flow,
|
|
};
|
|
output.push({
|
|
text: `${(tag.line + ':' + tag.column).padEnd(6)} ${flow.length > 0 ? '\u2795 ' : ''}${text}`,
|
|
tag: cetag,
|
|
});
|
|
if (fixes.length > 0) {
|
|
output.push({text: '\t\u21B3 \u001B[3m\uD83D\uDCA1 A Quick Fix is available for this issue\u001B[0m'});
|
|
}
|
|
if (flow.length > 0) {
|
|
output.push(
|
|
...flow
|
|
.filter(f => f.text)
|
|
.map((f, i) => ({
|
|
text: `\t\u21B3 ${flow.length > 1 ? ' ' + (i + 1) : ''} ${f.text}`,
|
|
})),
|
|
);
|
|
}
|
|
output.push({text: `\t\u001B[2m\u21B3 ${this.makeURL(tag.ruleKey, lang)}\u001B[0m`});
|
|
}
|
|
|
|
simplifyPathes(lines: string, inputFilepath?: string, pathPrefix?: string): string {
|
|
if (inputFilepath) {
|
|
lines = lines.replaceAll(inputFilepath, '<source>').replaceAll('<stdin>', '<source>');
|
|
}
|
|
if (pathPrefix) {
|
|
lines = lines.replaceAll(pathPrefix, '');
|
|
}
|
|
return lines;
|
|
}
|
|
|
|
parseOutputAndFixes(lines: string, inputFilepath?: string, pathPrefix?: string): ResultLine[] {
|
|
if (!lines) return [];
|
|
let output: ResultLine[] = [];
|
|
const lang: string = this.lang;
|
|
try {
|
|
const results = JSON.parse(this.simplifyPathes(lines, inputFilepath, pathPrefix));
|
|
if (results.header) {
|
|
output.push(
|
|
...utils.splitLines('\u001B[3m\u001B[32m' + results.header + '\u001B[0m').map(s => ({text: s})),
|
|
);
|
|
}
|
|
if (results.parsingErrors && results.parsingErrors.length > 0) {
|
|
output.push(
|
|
{text: ''},
|
|
{
|
|
text:
|
|
'\u001B[1m\u26A0\uFE0F ' +
|
|
'Parsing errors\u001B[0m (these can affect the quality of the analysis):',
|
|
},
|
|
);
|
|
for (const e of results.parsingErrors) {
|
|
this.readTag(e, output, 8, lang);
|
|
}
|
|
}
|
|
if (results.issues && results.issues.length > 0) {
|
|
output.push({text: ''}, {text: '\u001B[1m\uD83D\uDC1E Issues:\u001B[0m'});
|
|
for (const e of results.issues) {
|
|
this.readTag(e, output, 4, lang);
|
|
}
|
|
output.push({text: ''});
|
|
} else if (!results.parsingErrors || results.parsingErrors.length === 0) {
|
|
output.push({text: ''}, {text: '\u001B[1m\u2728 No issue found\u001B[0m'});
|
|
}
|
|
if (results.logs && results.logs.length > 0) {
|
|
output.push(
|
|
{text: ''},
|
|
{text: '\u001B[1m\uD83D\uDCDC Logs:\u001B[0m'},
|
|
...results.logs.map(l => ({text: `[${l.level}] ${l.message}`})),
|
|
);
|
|
}
|
|
if (results.reproducer) {
|
|
this.reproducer = {
|
|
content: results.reproducer.content,
|
|
type: 'application/octet-stream',
|
|
name: 'sonar-reproducer.zip',
|
|
title: 'reproducer',
|
|
};
|
|
}
|
|
} catch {
|
|
output = utils.splitLines(lines).map(l => ({text: l}));
|
|
}
|
|
return output;
|
|
}
|
|
|
|
buildCompilationCMD(
|
|
compilationInfo: Record<any, any>,
|
|
inputFilePath: string,
|
|
supportedLibraries?: Record<string, OptionsHandlerLibrary>,
|
|
) {
|
|
const cmd: any[] = [];
|
|
cmd.push(compilationInfo.compiler.exe);
|
|
|
|
// Collecting the flags of compilation
|
|
|
|
let compileFlags: string[] = splitArguments(compilationInfo.compiler.options);
|
|
const includeflags = super.getIncludeArguments(
|
|
compilationInfo.libraries,
|
|
supportedLibraries || {},
|
|
inputFilePath ? path.dirname(inputFilePath) : undefined,
|
|
);
|
|
compileFlags = compileFlags.concat(includeflags);
|
|
const libOptions = super.getLibraryOptions(compilationInfo.libraries, supportedLibraries || {});
|
|
compileFlags = compileFlags.concat(libOptions);
|
|
const manualCompileFlags = compilationInfo.options.filter(option => option !== inputFilePath);
|
|
compileFlags = compileFlags.concat(manualCompileFlags);
|
|
compileFlags = compileFlags.filter(f => f !== '');
|
|
|
|
return cmd.concat(compileFlags);
|
|
}
|
|
|
|
override getDefaultExecOptions(): ExecutionOptions {
|
|
const opts = super.getDefaultExecOptions();
|
|
// Allow bigger output for reproducer
|
|
opts.maxOutput = 2 * 1024 * 1024;
|
|
return opts;
|
|
}
|
|
|
|
override async runTool(
|
|
compilationInfo: CompilationInfo,
|
|
inputFilePath?: string,
|
|
args?: string[],
|
|
stdin?: string,
|
|
supportedLibraries?: Record<string, OptionsHandlerLibrary>,
|
|
): Promise<ToolResult> {
|
|
if (inputFilePath == null) {
|
|
return new Promise(resolve => {
|
|
resolve(this.createErrorResponse('Unable to run tool'));
|
|
});
|
|
}
|
|
this.reproducer = undefined;
|
|
this.lang = compilationInfo.compiler.lang === 'c' ? 'c' : 'cpp';
|
|
let sonarArgs: string[] = (args ?? [])
|
|
.filter(a => !a.includes('subprocess'))
|
|
.concat(['--directory', path.dirname(inputFilePath), '--']);
|
|
sonarArgs = sonarArgs.concat(this.buildCompilationCMD(compilationInfo, inputFilePath, supportedLibraries));
|
|
|
|
const res: ToolResult = await super.runTool(compilationInfo, inputFilePath, sonarArgs);
|
|
if (this.reproducer) {
|
|
res.artifact = this.reproducer;
|
|
}
|
|
return res;
|
|
}
|
|
}
|