diff --git a/apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts b/apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts index 504148b266..2a2b39b523 100644 --- a/apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts +++ b/apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts @@ -30,6 +30,27 @@ module.exports = { }, 'Static Analysis': function (browser: NightwatchBrowser) { runTests(browser) + }, + 'run analysis and filter results': function (browser: NightwatchBrowser) { + browser + .clickLaunchIcon('filePanel') + .click('*[data-id="treeViewLitreeViewItemcontracts"]') + .click('*[data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]') + .clickLaunchIcon('solidity') + .pause(10000) + .clickLaunchIcon('solidityStaticAnalysis') + .waitForElementPresent('#staticanalysisresult .warning', 5000) + .assert.containsText('#verticalIconsKindsolidityStaticAnalysis .remixui_status', '1') // Check warning count + .verify.elementPresent('input[name="showLibWarnings"]') + .verify.elementNotPresent('input[name="showLibWarnings"]:checked') + .verify.elementPresent('label[id="headingshowLibWarnings"]') + .click('label[id="headingshowLibWarnings"]') + .pause(1000) + .assert.containsText('#verticalIconsKindsolidityStaticAnalysis .remixui_status', '382') + .click('label[id="headingshowLibWarnings"]') + .pause(1000) + .assert.containsText('#verticalIconsKindsolidityStaticAnalysis .remixui_status', '1') + .end() } } @@ -47,14 +68,12 @@ function runTests (browser: NightwatchBrowser) { 'TooMuchGas.() : Variables have very similar names "test" and "test1".', 'TooMuchGas.() : Variables have very similar names "test" and "test1".'], '#staticanalysisresult .warning', - browser, function () { - browser.end() - } + browser ) }) } -function listSelectorContains (textsToFind: string[], selector: string, browser: NightwatchBrowser, callback: VoidFunction) { +function listSelectorContains (textsToFind: string[], selector: string, browser: NightwatchBrowser) { browser.execute(function (selector) { const items = document.querySelectorAll(selector) const ret = [] @@ -68,6 +87,5 @@ function listSelectorContains (textsToFind: string[], selector: string, browser: console.log('testing `' + result.value[k] + '` against `' + textsToFind[k] + '`') browser.assert.equal(result.value[k].indexOf(textsToFind[k]) !== -1, true) } - callback() }) } diff --git a/libs/remix-analyzer/src/solidity-analyzer/index.ts b/libs/remix-analyzer/src/solidity-analyzer/index.ts index da6b5bb35e..d2c45ff635 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/index.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/index.ts @@ -15,13 +15,13 @@ export default class staticAnalysisRunner { * @param toRun module indexes (compiled from remix IDE) * @param callback callback */ - run (compilationResult: CompilationResult, toRun: number[], callback: ((reports: AnalysisReport[]) => void)): void { + run (compilationResult: CompilationResult, toRun: number[]): AnalysisReport[] { const modules: ModuleObj[] = toRun.map((i) => { const Module = this.modules()[i] const m = new Module() return { name: m.name, mod: m } }) - this.runWithModuleList(compilationResult, modules, callback) + return this.runWithModuleList(compilationResult, modules) } /** @@ -30,7 +30,7 @@ export default class staticAnalysisRunner { * @param modules analysis module * @param callback callback */ - runWithModuleList (compilationResult: CompilationResult, modules: ModuleObj[], callback: ((reports: AnalysisReport[]) => void)): void { + runWithModuleList (compilationResult: CompilationResult, modules: ModuleObj[]): AnalysisReport[] { let reports: AnalysisReport[] = [] // Also provide convenience analysis via the AST walker. const walker = new AstWalker() @@ -64,7 +64,7 @@ export default class staticAnalysisRunner { } return { name: item.name, report: report } })) - callback(reports) + return reports } /** diff --git a/libs/remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.4.24.ts b/libs/remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.4.24.ts index 67607d7a35..8052a1870a 100644 --- a/libs/remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.4.24.ts +++ b/libs/remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.4.24.ts @@ -814,12 +814,11 @@ test('Integration test forLoopIteratesOverDynamicArray module', function (t: tes function runModuleOnFiles (Module: any, t: test.Test, cb: ((fname: string, report: AnalysisReportObj[]) => void)): void { const statRunner: StatRunner = new StatRunner() testFiles.forEach((fileName: string) => { - statRunner.runWithModuleList(compilationResults[fileName], [{ name: new Module().name, mod: new Module() }], (reports: AnalysisReport[]) => { - let report: AnalysisReportObj[] = reports[0].report - if (report.some((x: AnalysisReportObj) => x.warning.includes('INTERNAL ERROR'))) { - t.comment('Error while executing Module: ' + JSON.stringify(report)) - } - cb(fileName, report) - }) + const reports = statRunner.runWithModuleList(compilationResults[fileName], [{ name: new Module().name, mod: new Module() }]) + let report: AnalysisReportObj[] = reports[0].report + if (report.some((x: AnalysisReportObj) => x.warning.includes('INTERNAL ERROR'))) { + t.comment('Error while executing Module: ' + JSON.stringify(report)) + } + cb(fileName, report) }) } diff --git a/libs/remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.5.0.ts b/libs/remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.5.0.ts index 780cfa2b63..aa14866a69 100644 --- a/libs/remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.5.0.ts +++ b/libs/remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.5.0.ts @@ -816,12 +816,11 @@ test('Integration test forLoopIteratesOverDynamicArray module', function (t: tes function runModuleOnFiles (Module: any, t: test.Test, cb: ((fname: string, report: AnalysisReportObj[]) => void)): void { const statRunner: StatRunner = new StatRunner() testFiles.forEach((fileName: string) => { - statRunner.runWithModuleList(compilationResults[fileName], [{ name: new Module().name, mod: new Module() }], (reports: AnalysisReport[]) => { - let report: AnalysisReportObj[] = reports[0].report - if (report.some((x: AnalysisReportObj) => x['warning'].includes('INTERNAL ERROR'))) { - t.comment('Error while executing Module: ' + JSON.stringify(report)) - } - cb(fileName, report) - }) + const reports = statRunner.runWithModuleList(compilationResults[fileName], [{ name: new Module().name, mod: new Module() }]) + let report: AnalysisReportObj[] = reports[0].report + if (report.some((x: AnalysisReportObj) => x['warning'].includes('INTERNAL ERROR'))) { + t.comment('Error while executing Module: ' + JSON.stringify(report)) + } + cb(fileName, report) }) } diff --git a/libs/remix-analyzer/test/analysis/staticAnalysisIssues-test-0.4.24.ts b/libs/remix-analyzer/test/analysis/staticAnalysisIssues-test-0.4.24.ts index de6303ef89..2a6091dc32 100644 --- a/libs/remix-analyzer/test/analysis/staticAnalysisIssues-test-0.4.24.ts +++ b/libs/remix-analyzer/test/analysis/staticAnalysisIssues-test-0.4.24.ts @@ -31,10 +31,9 @@ test('staticAnalysisIssues.functionParameterPassingError', function (t) { const statRunner: StatRunner = new StatRunner() t.doesNotThrow(() => { - statRunner.runWithModuleList(res, [{ name: new Module().name, mod: new Module() }], (reports: AnalysisReport[]) => {}) + statRunner.runWithModuleList(res, [{ name: new Module().name, mod: new Module()}]) }, 'Analysis should not throw') - statRunner.runWithModuleList(res, [{ name: new Module().name, mod: new Module() }], (reports: AnalysisReport[]) => { - t.ok(!reports.some((mod: AnalysisReport) => mod.report.some((rep: AnalysisReportObj) => rep.warning.includes('INTERNAL ERROR')), 'Should not have internal errors')) - }) + const reports = statRunner.runWithModuleList(res, [{ name: new Module().name, mod: new Module()}]) + t.ok(!reports.some((mod: AnalysisReport) => mod.report.some((rep: AnalysisReportObj) => rep.warning.includes('INTERNAL ERROR')), 'Should not have internal errors')) }) diff --git a/libs/remix-analyzer/test/analysis/staticAnalysisIssues-test-0.5.0.ts b/libs/remix-analyzer/test/analysis/staticAnalysisIssues-test-0.5.0.ts index beac78be6d..43234e4168 100644 --- a/libs/remix-analyzer/test/analysis/staticAnalysisIssues-test-0.5.0.ts +++ b/libs/remix-analyzer/test/analysis/staticAnalysisIssues-test-0.5.0.ts @@ -31,10 +31,9 @@ test('staticAnalysisIssues.functionParameterPassingError', function (t) { const statRunner: StatRunner = new StatRunner() t.doesNotThrow(() => { - statRunner.runWithModuleList(res, [{ name: new Module().name, mod: new Module() }], (reports: AnalysisReport[]) => {}) + statRunner.runWithModuleList(res, [{ name: new Module().name, mod: new Module() }]) }, 'Analysis should not throw') - statRunner.runWithModuleList(res, [{ name: new Module().name, mod: new Module() }], (reports: AnalysisReport[]) => { - t.ok(!reports.some((mod: AnalysisReport) => mod.report.some((rep: AnalysisReportObj) => rep.warning.includes('INTERNAL ERROR')), 'Should not have internal errors')) - }) + const reports = statRunner.runWithModuleList(res, [{ name: new Module().name, mod: new Module() }]) + t.ok(!reports.some((mod: AnalysisReport) => mod.report.some((rep: AnalysisReportObj) => rep.warning.includes('INTERNAL ERROR')), 'Should not have internal errors')) }) diff --git a/libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx b/libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx index 95913e533b..4b0d4fa9be 100644 --- a/libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx +++ b/libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx @@ -12,6 +12,7 @@ export interface RemixUiCheckboxProps { id?: string itemName?: string categoryId?: string + title?: string visibility?: string display?: string } @@ -26,11 +27,12 @@ export const RemixUiCheckbox = ({ onChange, itemName, categoryId, + title, visibility, display = 'flex' }: RemixUiCheckboxProps) => { return ( -
+
{ const [autoRun, setAutoRun] = useState(true) const [slitherEnabled, setSlitherEnabled] = useState(false) const [showSlither, setShowSlither] = useState(false) + let [showLibsWarning, setShowLibsWarning] = useState(false) // eslint-disable-line prefer-const const [categoryIndex, setCategoryIndex] = useState(groupedModuleIndex(groupedModules)) - - const warningContainer = React.useRef(null) const [warningState, setWarningState] = useState({}) + + const warningContainer = useRef(null) + const allWarnings = useRef({}) const [state, dispatch] = useReducer(analysisReducer, initialState) useEffect(() => { @@ -76,9 +78,12 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { useEffect(() => { setWarningState({}) + const runAnalysis = async () => { + await run(state.data, state.source, state.file) + } if (autoRun) { if (state.data !== null) { - run(state.data, state.source, state.file) + runAnalysis().catch(console.error); } } else { props.event.trigger('staticAnaysisWarning', []) @@ -131,6 +136,30 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { ) } + const filterWarnings = () => { + let newWarningState = {} + let newWarningCount = 0 + if (showLibsWarning) { + for (const category in allWarnings.current) + newWarningCount = newWarningCount + allWarnings.current[category].length + newWarningState = allWarnings.current + } + else { + for (const category in allWarnings.current) { + const warnings = allWarnings.current[category] + newWarningState[category] = [] + for (const warning of warnings) { + if (!warning.options.isLibrary) { + newWarningCount++ + newWarningState[category].push(warning) + } + } + } + } + props.event.trigger('staticAnaysisWarning', [newWarningCount]) + setWarningState(newWarningState) + } + const showWarnings = (warningMessage, groupByKey) => { const resultArray = [] warningMessage.map(x => { @@ -149,143 +178,149 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { } const groupedCategory = groupBy(resultArray, groupByKey) - setWarningState(groupedCategory) + allWarnings.current = groupedCategory + filterWarnings() } - const run = (lastCompilationResult, lastCompilationSource, currentFile) => { + const run = async (lastCompilationResult, lastCompilationSource, currentFile) => { if (state.data !== null) { if (lastCompilationResult && (categoryIndex.length > 0 || slitherEnabled)) { - let warningCount = 0 const warningMessage = [] const warningErrors = [] // Remix Analysis _paq.push(['trackEvent', 'solidityStaticAnalyzer', 'analyzeWithRemixAnalyzer']) - runner.run(lastCompilationResult, categoryIndex, results => { - results.map((result) => { - let moduleName - Object.keys(groupedModules).map(key => { - groupedModules[key].forEach(el => { - if (el.name === result.name) { - moduleName = groupedModules[key][0].categoryDisplayName - } - }) - }) - result.report.map((item) => { - let location: any = {} - let locationString = 'not available' - let column = 0 - let row = 0 - let fileName = currentFile - if (item.location) { - const split = item.location.split(':') - const file = split[2] - location = { - start: parseInt(split[0]), - length: parseInt(split[1]) - } - location = props.analysisModule._deps.offsetToLineColumnConverter.offsetToLineColumn( - location, - parseInt(file), - lastCompilationSource.sources, - lastCompilationResult.sources - ) - row = location.start.line - column = location.start.column - locationString = row + 1 + ':' + column + ':' - fileName = Object.keys(lastCompilationResult.sources)[file] - } - warningCount++ - const msg = message(result.name, item.warning, item.more, fileName, locationString) - const options = { - type: 'warning', - useSpan: true, - errFile: fileName, - fileName, - errLine: row, - errCol: column, - item: item, - name: result.name, - locationString, - more: item.more, - location: location + const results = runner.run(lastCompilationResult, categoryIndex) + for (const result of results) { + let moduleName + Object.keys(groupedModules).map(key => { + groupedModules[key].forEach(el => { + if (el.name === result.name) { + moduleName = groupedModules[key][0].categoryDisplayName } - warningErrors.push(options) - warningMessage.push({ msg, options, hasWarning: true, warningModuleName: moduleName }) }) }) - // Slither Analysis - if (slitherEnabled) { - props.analysisModule.call('solidity', 'getCompilerState').then((compilerState) => { - const { currentVersion, optimize, evmVersion } = compilerState - props.analysisModule.call('terminal', 'log', { type: 'info', value: '[Slither Analysis]: Running...' }) - _paq.push(['trackEvent', 'solidityStaticAnalyzer', 'analyzeWithSlither']) - props.analysisModule.call('slither', 'analyse', state.file, { currentVersion, optimize, evmVersion }).then(async (result) => { - if (result.status) { - props.analysisModule.call('terminal', 'log', { type: 'info', value: `[Slither Analysis]: Analysis Completed!! ${result.count} warnings found.` }) - const report = result.data - for (const item of report) { - let location: any = {} - let locationString = 'not available' - let column = 0 - let row = 0 - let fileName = currentFile + for (const item of result.report) { + let location: any = {} + let locationString = 'not available' + let column = 0 + let row = 0 + let fileName = currentFile + let isLibrary = false - if (item.sourceMap && item.sourceMap.length) { - let path = item.sourceMap[0].source_mapping.filename_relative - let fileIndex = Object.keys(lastCompilationResult.sources).indexOf(path) - if (fileIndex === -1) { - path = await props.analysisModule.call('fileManager', 'getUrlFromPath', path) - fileIndex = Object.keys(lastCompilationResult.sources).indexOf(path.file) - } - if (fileIndex >= 0) { - location = { - start: item.sourceMap[0].source_mapping.start, - length: item.sourceMap[0].source_mapping.length - } - location = props.analysisModule._deps.offsetToLineColumnConverter.offsetToLineColumn( - location, - fileIndex, - lastCompilationSource.sources, - lastCompilationResult.sources - ) - row = location.start.line - column = location.start.column - locationString = row + 1 + ':' + column + ':' - fileName = Object.keys(lastCompilationResult.sources)[fileIndex] - } - } - warningCount++ - const msg = message(item.title, item.description, item.more, fileName, locationString) - const options = { - type: 'warning', - useSpan: true, - errFile: fileName, - fileName, - errLine: row, - errCol: column, - item: { warning: item.description }, - name: item.title, - locationString, - more: item.more, - location: location + if (item.location) { + const split = item.location.split(':') + const file = split[2] + location = { + start: parseInt(split[0]), + length: parseInt(split[1]) + } + location = props.analysisModule._deps.offsetToLineColumnConverter.offsetToLineColumn( + location, + parseInt(file), + lastCompilationSource.sources, + lastCompilationResult.sources + ) + row = location.start.line + column = location.start.column + locationString = row + 1 + ':' + column + ':' + fileName = Object.keys(lastCompilationResult.sources)[file] + } + if(fileName !== currentFile) { + const {file, provider} = await props.analysisModule.call('fileManager', 'getPathFromUrl', fileName) + if (file.startsWith('.deps') || (provider.type === 'localhost' && file.startsWith('localhost/node_modules'))) isLibrary = true + } + const msg = message(result.name, item.warning, item.more, fileName, locationString) + const options = { + type: 'warning', + useSpan: true, + errFile: fileName, + fileName, + isLibrary, + errLine: row, + errCol: column, + item: item, + name: result.name, + locationString, + more: item.more, + location: location + } + warningErrors.push(options) + warningMessage.push({ msg, options, hasWarning: true, warningModuleName: moduleName }) + } + } + // Slither Analysis + if (slitherEnabled) { + try { + const compilerState = await props.analysisModule.call('solidity', 'getCompilerState') + const { currentVersion, optimize, evmVersion } = compilerState + await props.analysisModule.call('terminal', 'log', { type: 'info', value: '[Slither Analysis]: Running...' }) + _paq.push(['trackEvent', 'solidityStaticAnalyzer', 'analyzeWithSlither']) + const result = await props.analysisModule.call('slither', 'analyse', state.file, { currentVersion, optimize, evmVersion }) + if (result.status) { + props.analysisModule.call('terminal', 'log', { type: 'info', value: `[Slither Analysis]: Analysis Completed!! ${result.count} warnings found.` }) + const report = result.data + for (const item of report) { + let location: any = {} + let locationString = 'not available' + let column = 0 + let row = 0 + let fileName = currentFile + let isLibrary = false + + if (item.sourceMap && item.sourceMap.length) { + let path = item.sourceMap[0].source_mapping.filename_relative + let fileIndex = Object.keys(lastCompilationResult.sources).indexOf(path) + if (fileIndex === -1) { + path = await props.analysisModule.call('fileManager', 'getUrlFromPath', path) + fileIndex = Object.keys(lastCompilationResult.sources).indexOf(path.file) + } + if (fileIndex >= 0) { + location = { + start: item.sourceMap[0].source_mapping.start, + length: item.sourceMap[0].source_mapping.length } - warningErrors.push(options) - warningMessage.push({ msg, options, hasWarning: true, warningModuleName: 'Slither Analysis' }) + location = props.analysisModule._deps.offsetToLineColumnConverter.offsetToLineColumn( + location, + fileIndex, + lastCompilationSource.sources, + lastCompilationResult.sources + ) + row = location.start.line + column = location.start.column + locationString = row + 1 + ':' + column + ':' + fileName = Object.keys(lastCompilationResult.sources)[fileIndex] } - showWarnings(warningMessage, 'warningModuleName') - props.event.trigger('staticAnaysisWarning', [warningCount]) } - }).catch(() => { - props.analysisModule.call('terminal', 'log', { type: 'error', value: '[Slither Analysis]: Error occured! See remixd console for details.' }) - showWarnings(warningMessage, 'warningModuleName') - }) - }) - } else { + if(fileName !== currentFile) { + const {file, provider} = await props.analysisModule.call('fileManager', 'getPathFromUrl', fileName) + if (file.startsWith('.deps') || (provider.type === 'localhost' && file.startsWith('localhost/node_modules'))) isLibrary = true + } + const msg = message(item.title, item.description, item.more, fileName, locationString) + const options = { + type: 'warning', + useSpan: true, + errFile: fileName, + fileName, + isLibrary, + errLine: row, + errCol: column, + item: { warning: item.description }, + name: item.title, + locationString, + more: item.more, + location: location + } + warningErrors.push(options) + warningMessage.push({ msg, options, hasWarning: true, warningModuleName: 'Slither Analysis' }) + } + showWarnings(warningMessage, 'warningModuleName') + } + } catch(error) { + props.analysisModule.call('terminal', 'log', { type: 'error', value: '[Slither Analysis]: Error occured! See remixd console for details.' }) showWarnings(warningMessage, 'warningModuleName') - props.event.trigger('staticAnaysisWarning', [warningCount]) } - }) + } else showWarnings(warningMessage, 'warningModuleName') } else { if (categoryIndex.length) { warningContainer.current.innerText = 'No compiled AST available' @@ -346,6 +381,17 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { } } + const handleShowLibsWarning = () => { + if (showLibsWarning) { + showLibsWarning = false + setShowLibsWarning(false) + } else { + showLibsWarning = true + setShowLibsWarning(true) + } + filterWarnings() + } + const categoryItem = (categoryId, item, i) => { return (
@@ -409,6 +455,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { { return (value.map(x => { return x._index.toString() @@ -421,12 +468,13 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { {}} /> -
{ showSlither &&
@@ -469,14 +517,25 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { {state.file}
-
{Object.entries(warningState).length > 0 &&
+ {}} + /> +
{ (Object.entries(warningState).map((element, index) => (
- {element[0]} + {element[1]['length'] > 0 ? {element[0]} : null} {element[1]['map']((x, i) => ( // eslint-disable-line dot-notation x.hasWarning ? ( // eslint-disable-next-line dot-notation